/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "SpellInfo.h"
#include "Battleground.h"
#include "Containers.h"
#include "Corpse.h"
#include "DB2Stores.h"
#include "FlatSet.h"
#include "GameTables.h"
#include "InstanceScript.h"
#include "Item.h"
#include "ItemTemplate.h"
#include "Log.h"
#include "LootMgr.h"
#include "Map.h"
#include "ObjectAccessor.h"
#include "Pet.h"
#include "Player.h"
#include "Random.h"
#include "Spell.h"
#include "SpellAuraEffects.h"
#include "SpellMgr.h"
#include "Vehicle.h"
#include <G3D/g3dmath.h>

uint32 GetTargetFlagMask(SpellTargetObjectTypes objType)
{
    switch (objType)
    {
        case TARGET_OBJECT_TYPE_DEST:
            return TARGET_FLAG_DEST_LOCATION;
        case TARGET_OBJECT_TYPE_UNIT_AND_DEST:
            return TARGET_FLAG_DEST_LOCATION | TARGET_FLAG_UNIT;
        case TARGET_OBJECT_TYPE_CORPSE_ALLY:
            return TARGET_FLAG_CORPSE_ALLY;
        case TARGET_OBJECT_TYPE_CORPSE_ENEMY:
            return TARGET_FLAG_CORPSE_ENEMY;
        case TARGET_OBJECT_TYPE_CORPSE:
            return TARGET_FLAG_CORPSE_ALLY | TARGET_FLAG_CORPSE_ENEMY;
        case TARGET_OBJECT_TYPE_UNIT:
            return TARGET_FLAG_UNIT;
        case TARGET_OBJECT_TYPE_GOBJ:
            return TARGET_FLAG_GAMEOBJECT;
        case TARGET_OBJECT_TYPE_GOBJ_ITEM:
            return TARGET_FLAG_GAMEOBJECT_ITEM;
        case TARGET_OBJECT_TYPE_ITEM:
            return TARGET_FLAG_ITEM;
        case TARGET_OBJECT_TYPE_SRC:
            return TARGET_FLAG_SOURCE_LOCATION;
        default:
            return TARGET_FLAG_NONE;
    }
}

SpellImplicitTargetInfo::SpellImplicitTargetInfo(uint32 target)
{
    _target = Targets(target);
}

bool SpellImplicitTargetInfo::IsArea() const
{
    return GetSelectionCategory() == TARGET_SELECT_CATEGORY_AREA || GetSelectionCategory() == TARGET_SELECT_CATEGORY_CONE;
}

SpellTargetSelectionCategories SpellImplicitTargetInfo::GetSelectionCategory() const
{
    return _data[_target].SelectionCategory;
}

SpellTargetReferenceTypes SpellImplicitTargetInfo::GetReferenceType() const
{
    return _data[_target].ReferenceType;
}

SpellTargetObjectTypes SpellImplicitTargetInfo::GetObjectType() const
{
    return _data[_target].ObjectType;
}

SpellTargetCheckTypes SpellImplicitTargetInfo::GetCheckType() const
{
    return _data[_target].SelectionCheckType;
}

SpellTargetDirectionTypes SpellImplicitTargetInfo::GetDirectionType() const
{
    return _data[_target].DirectionType;
}

float SpellImplicitTargetInfo::CalcDirectionAngle() const
{
    switch (GetDirectionType())
    {
        case TARGET_DIR_FRONT:
            return 0.0f;
        case TARGET_DIR_BACK:
            return static_cast<float>(M_PI);
        case TARGET_DIR_RIGHT:
            return static_cast<float>(-M_PI/2);
        case TARGET_DIR_LEFT:
            return static_cast<float>(M_PI/2);
        case TARGET_DIR_FRONT_RIGHT:
            return static_cast<float>(-M_PI/4);
        case TARGET_DIR_BACK_RIGHT:
            return static_cast<float>(-3*M_PI/4);
        case TARGET_DIR_BACK_LEFT:
            return static_cast<float>(3*M_PI/4);
        case TARGET_DIR_FRONT_LEFT:
            return static_cast<float>(M_PI/4);
        case TARGET_DIR_RANDOM:
            return rand_norm() * static_cast<float>(2 * M_PI);
        default:
            return 0.0f;
    }
}

Targets SpellImplicitTargetInfo::GetTarget() const
{
    return _target;
}

uint32 SpellImplicitTargetInfo::GetExplicitTargetMask(bool& srcSet, bool& dstSet) const
{
    uint32 targetMask = 0;
    if (GetTarget() == TARGET_DEST_TRAJ)
    {
        if (!srcSet)
            targetMask = TARGET_FLAG_SOURCE_LOCATION;
        if (!dstSet)
            targetMask |= TARGET_FLAG_DEST_LOCATION;
    }
    else
    {
        switch (GetReferenceType())
        {
            case TARGET_REFERENCE_TYPE_SRC:
                if (srcSet)
                    break;
                targetMask = TARGET_FLAG_SOURCE_LOCATION;
                break;
            case TARGET_REFERENCE_TYPE_DEST:
                if (dstSet)
                    break;
                targetMask = TARGET_FLAG_DEST_LOCATION;
                break;
            case TARGET_REFERENCE_TYPE_TARGET:
                switch (GetObjectType())
                {
                    case TARGET_OBJECT_TYPE_GOBJ:
                        targetMask = TARGET_FLAG_GAMEOBJECT;
                        break;
                    case TARGET_OBJECT_TYPE_GOBJ_ITEM:
                        targetMask = TARGET_FLAG_GAMEOBJECT_ITEM;
                        break;
                    case TARGET_OBJECT_TYPE_UNIT_AND_DEST:
                    case TARGET_OBJECT_TYPE_UNIT:
                    case TARGET_OBJECT_TYPE_DEST:
                        switch (GetCheckType())
                        {
                            case TARGET_CHECK_ENEMY:
                                targetMask = TARGET_FLAG_UNIT_ENEMY;
                                break;
                            case TARGET_CHECK_ALLY:
                                targetMask = TARGET_FLAG_UNIT_ALLY;
                                break;
                            case TARGET_CHECK_PARTY:
                                targetMask = TARGET_FLAG_UNIT_PARTY;
                                break;
                            case TARGET_CHECK_RAID:
                                targetMask = TARGET_FLAG_UNIT_RAID;
                                break;
                            case TARGET_CHECK_PASSENGER:
                                targetMask = TARGET_FLAG_UNIT_PASSENGER;
                                break;
                            case TARGET_CHECK_RAID_CLASS:
                                [[fallthrough]];
                            default:
                                targetMask = TARGET_FLAG_UNIT;
                                break;
                        }
                        break;
                    default:
                        break;
                }
                break;
            default:
                break;
        }
    }

    switch (GetObjectType())
    {
        case TARGET_OBJECT_TYPE_SRC:
            srcSet = true;
            break;
        case TARGET_OBJECT_TYPE_DEST:
        case TARGET_OBJECT_TYPE_UNIT_AND_DEST:
            dstSet = true;
            break;
        default:
            break;
    }
    return targetMask;
}

struct SpellEffectInfo::ImmunityInfo
{
    ImmunityInfo() = default;
    ~ImmunityInfo() = default;

    ImmunityInfo(ImmunityInfo const&) = delete;
    ImmunityInfo(ImmunityInfo&&) noexcept = delete;
    ImmunityInfo& operator=(ImmunityInfo const&) = delete;
    ImmunityInfo& operator=(ImmunityInfo&&) noexcept = delete;

    uint32 SchoolImmuneMask = 0;
    uint32 ApplyHarmfulAuraImmuneMask = 0;
    uint64 MechanicImmuneMask = 0;
    uint32 DispelImmuneMask = 0;
    uint32 DamageSchoolMask = 0;
    uint8 OtherImmuneMask = 0;

    Trinity::Containers::FlatSet<AuraType> AuraTypeImmune;
    Trinity::Containers::FlatSet<SpellEffectName> SpellEffectImmune;
};

std::array<SpellImplicitTargetInfo::StaticData, TOTAL_SPELL_TARGETS> SpellImplicitTargetInfo::_data =
{ {
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        //
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 1 TARGET_UNIT_CASTER
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 2 TARGET_UNIT_NEARBY_ENEMY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_ALLY,     TARGET_DIR_NONE},        // 3 TARGET_UNIT_NEARBY_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_PARTY,    TARGET_DIR_NONE},        // 4 TARGET_UNIT_NEARBY_PARTY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 5 TARGET_UNIT_PET
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 6 TARGET_UNIT_TARGET_ENEMY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_SRC,    TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ENTRY,    TARGET_DIR_NONE},        // 7 TARGET_UNIT_SRC_AREA_ENTRY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ENTRY,    TARGET_DIR_NONE},        // 8 TARGET_UNIT_DEST_AREA_ENTRY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 9 TARGET_DEST_HOME
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 10
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_SRC,    TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 11 TARGET_UNIT_SRC_AREA_UNK_11
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 12
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 13
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 14
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_SRC,    TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 15 TARGET_UNIT_SRC_AREA_ENEMY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 16 TARGET_UNIT_DEST_AREA_ENEMY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 17 TARGET_DEST_DB
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 18 TARGET_DEST_CASTER
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 19
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_PARTY,    TARGET_DIR_NONE},        // 20 TARGET_UNIT_CASTER_AREA_PARTY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_ALLY,     TARGET_DIR_NONE},        // 21 TARGET_UNIT_TARGET_ALLY
    {TARGET_OBJECT_TYPE_SRC,  TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 22 TARGET_SRC_CASTER
    {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 23 TARGET_GAMEOBJECT_TARGET
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ENEMY,    TARGET_DIR_FRONT},       // 24 TARGET_UNIT_CONE_ENEMY_24
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 25 TARGET_UNIT_TARGET_ANY
    {TARGET_OBJECT_TYPE_GOBJ_ITEM, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE},    // 26 TARGET_GAMEOBJECT_ITEM_TARGET
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 27 TARGET_UNIT_MASTER
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 28 TARGET_DEST_DYNOBJ_ENEMY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_ALLY,     TARGET_DIR_NONE},        // 29 TARGET_DEST_DYNOBJ_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_SRC,    TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ALLY,     TARGET_DIR_NONE},        // 30 TARGET_UNIT_SRC_AREA_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ALLY,     TARGET_DIR_NONE},        // 31 TARGET_UNIT_DEST_AREA_ALLY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT_LEFT},  // 32 TARGET_DEST_CASTER_SUMMON
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_SRC,    TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_PARTY,    TARGET_DIR_NONE},        // 33 TARGET_UNIT_SRC_AREA_PARTY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_PARTY,    TARGET_DIR_NONE},        // 34 TARGET_UNIT_DEST_AREA_PARTY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_PARTY,    TARGET_DIR_NONE},        // 35 TARGET_UNIT_TARGET_PARTY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 36 TARGET_DEST_CASTER_UNK_36
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_LAST,   TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_PARTY,    TARGET_DIR_NONE},        // 37 TARGET_UNIT_LASTTARGET_AREA_PARTY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_ENTRY,    TARGET_DIR_NONE},        // 38 TARGET_UNIT_NEARBY_ENTRY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 39 TARGET_DEST_CASTER_FISHING
    {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_ENTRY,    TARGET_DIR_NONE},        // 40 TARGET_GAMEOBJECT_NEARBY_ENTRY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT_RIGHT}, // 41 TARGET_DEST_CASTER_FRONT_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK_RIGHT},  // 42 TARGET_DEST_CASTER_BACK_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK_LEFT},   // 43 TARGET_DEST_CASTER_BACK_LEFT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT_LEFT},  // 44 TARGET_DEST_CASTER_FRONT_LEFT
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_ALLY,     TARGET_DIR_NONE},        // 45 TARGET_UNIT_TARGET_CHAINHEAL_ALLY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_ENTRY,    TARGET_DIR_NONE},        // 46 TARGET_DEST_NEARBY_ENTRY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT},       // 47 TARGET_DEST_CASTER_FRONT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK},        // 48 TARGET_DEST_CASTER_BACK
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RIGHT},       // 49 TARGET_DEST_CASTER_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_LEFT},        // 50 TARGET_DEST_CASTER_LEFT
    {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_SRC,    TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 51 TARGET_GAMEOBJECT_SRC_AREA
    {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 52 TARGET_GAMEOBJECT_DEST_AREA
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 53 TARGET_DEST_TARGET_ENEMY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ENEMY,    TARGET_DIR_FRONT},       // 54 TARGET_UNIT_CONE_180_DEG_ENEMY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 55 TARGET_DEST_CASTER_FRONT_LEAP
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_RAID,     TARGET_DIR_NONE},        // 56 TARGET_UNIT_CASTER_AREA_RAID
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_RAID,     TARGET_DIR_NONE},        // 57 TARGET_UNIT_TARGET_RAID
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_RAID,     TARGET_DIR_NONE},        // 58 TARGET_UNIT_NEARBY_RAID
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ALLY,     TARGET_DIR_FRONT},       // 59 TARGET_UNIT_CONE_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ENTRY,    TARGET_DIR_FRONT},       // 60 TARGET_UNIT_CONE_ENTRY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_RAID_CLASS, TARGET_DIR_NONE},      // 61 TARGET_UNIT_TARGET_AREA_RAID_CLASS
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 62 TARGET_DEST_CASTER_GROUND
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 63 TARGET_DEST_TARGET_ANY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT},       // 64 TARGET_DEST_TARGET_FRONT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK},        // 65 TARGET_DEST_TARGET_BACK
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RIGHT},       // 66 TARGET_DEST_TARGET_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_LEFT},        // 67 TARGET_DEST_TARGET_LEFT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT_RIGHT}, // 68 TARGET_DEST_TARGET_FRONT_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK_RIGHT},  // 69 TARGET_DEST_TARGET_BACK_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK_LEFT},   // 70 TARGET_DEST_TARGET_BACK_LEFT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT_LEFT},  // 71 TARGET_DEST_TARGET_FRONT_LEFT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RANDOM},      // 72 TARGET_DEST_CASTER_RANDOM
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RANDOM},      // 73 TARGET_DEST_CASTER_RADIUS
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RANDOM},      // 74 TARGET_DEST_TARGET_RANDOM
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RANDOM},      // 75 TARGET_DEST_TARGET_RADIUS
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CHANNEL, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 76 TARGET_DEST_CHANNEL_TARGET
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CHANNEL, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 77 TARGET_UNIT_CHANNEL_TARGET
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT},       // 78 TARGET_DEST_DEST_FRONT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK},        // 79 TARGET_DEST_DEST_BACK
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RIGHT},       // 80 TARGET_DEST_DEST_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_LEFT},        // 81 TARGET_DEST_DEST_LEFT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT_RIGHT}, // 82 TARGET_DEST_DEST_FRONT_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK_RIGHT},  // 83 TARGET_DEST_DEST_BACK_RIGHT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_BACK_LEFT},   // 84 TARGET_DEST_DEST_BACK_LEFT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT_LEFT},  // 85 TARGET_DEST_DEST_FRONT_LEFT
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RANDOM},      // 86 TARGET_DEST_DEST_RANDOM
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 87 TARGET_DEST_DEST
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 88 TARGET_DEST_DYNOBJ_NONE
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_TRAJ,    TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 89 TARGET_DEST_TRAJ
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 90 TARGET_UNIT_TARGET_MINIPET
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RANDOM},      // 91 TARGET_DEST_DEST_RADIUS
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 92 TARGET_UNIT_SUMMONER
    {TARGET_OBJECT_TYPE_CORPSE, TARGET_REFERENCE_TYPE_SRC,  TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 93 TARGET_CORPSE_SRC_AREA_ENEMY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 94 TARGET_UNIT_VEHICLE
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_PASSENGER, TARGET_DIR_NONE},       // 95 TARGET_UNIT_TARGET_PASSENGER
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 96 TARGET_UNIT_PASSENGER_0
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 97 TARGET_UNIT_PASSENGER_1
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 98 TARGET_UNIT_PASSENGER_2
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 99 TARGET_UNIT_PASSENGER_3
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 100 TARGET_UNIT_PASSENGER_4
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 101 TARGET_UNIT_PASSENGER_5
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 102 TARGET_UNIT_PASSENGER_6
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 103 TARGET_UNIT_PASSENGER_7
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ENEMY,    TARGET_DIR_FRONT},       // 104 TARGET_UNIT_CONE_CASTER_TO_DEST_ENEMY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 105 TARGET_UNIT_CASTER_AND_PASSENGERS
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CHANNEL, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 106 TARGET_DEST_CHANNEL_CASTER
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_ENTRY,    TARGET_DIR_NONE},        // 107 TARGET_DEST_NEARBY_ENTRY_2
    {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ENEMY,    TARGET_DIR_FRONT},       // 108 TARGET_GAMEOBJECT_CONE_CASTER_TO_DEST_ENEMY
    {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ALLY,     TARGET_DIR_FRONT},       // 109 TARGET_GAMEOBJECT_CONE_CASTER_TO_DEST_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ENTRY,    TARGET_DIR_FRONT},       // 110 TARGET_UNIT_CONE_CASTER_TO_DEST_ENTRY
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 111
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 112
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 113
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 114
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_SRC,    TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 115 TARGET_UNIT_SRC_AREA_FURTHEST_ENEMY
    {TARGET_OBJECT_TYPE_UNIT_AND_DEST, TARGET_REFERENCE_TYPE_LAST, TARGET_SELECT_CATEGORY_AREA, TARGET_CHECK_ENEMY, TARGET_DIR_NONE},       // 116 TARGET_UNIT_AND_DEST_LAST_ENEMY
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 117
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_RAID,     TARGET_DIR_NONE},        // 118 TARGET_UNIT_TARGET_ALLY_OR_RAID
    {TARGET_OBJECT_TYPE_CORPSE, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA,  TARGET_CHECK_RAID,     TARGET_DIR_NONE},        // 119 TARGET_CORPSE_SRC_AREA_RAID
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_SUMMONED, TARGET_DIR_NONE},        // 120 TARGET_UNIT_SELF_AND_SUMMONS
    {TARGET_OBJECT_TYPE_CORPSE, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_ALLY,   TARGET_DIR_NONE},        // 121 TARGET_CORPSE_TARGET_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 122 TARGET_UNIT_AREA_THREAT_LIST
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 123 TARGET_UNIT_AREA_TAP_LIST
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 124 TARGET_UNIT_TARGET_TAP_LIST
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 125 TARGET_DEST_CASTER_GROUND_2
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 126 TARGET_UNIT_CASTER_AREA_ENEMY_CLUMP
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 127 TARGET_DEST_CASTER_ENEMY_CLUMP_CENTROID
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ALLY,     TARGET_DIR_FRONT},       // 128 TARGET_UNIT_RECT_CASTER_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ENEMY,    TARGET_DIR_FRONT},       // 129 TARGET_UNIT_RECT_CASTER_ENEMY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_DEFAULT,  TARGET_DIR_FRONT},       // 130 TARGET_UNIT_RECT_CASTER
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 131 TARGET_DEST_SUMMONER
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_TARGET, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_ALLY,     TARGET_DIR_NONE},        // 132 TARGET_DEST_TARGET_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_LINE,    TARGET_CHECK_ALLY,     TARGET_DIR_NONE},        // 133 TARGET_UNIT_LINE_CASTER_TO_DEST_ALLY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_LINE,    TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 134 TARGET_UNIT_LINE_CASTER_TO_DEST_ENEMY
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_LINE,    TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 135 TARGET_UNIT_LINE_CASTER_TO_DEST
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_CONE,    TARGET_CHECK_ALLY,     TARGET_DIR_FRONT},       // 136 TARGET_UNIT_CONE_CASTER_TO_DEST_ALLY
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 137 TARGET_DEST_CASTER_MOVEMENT_DIRECTION
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 138 TARGET_DEST_DEST_GROUND
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 139
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 140 TARGET_DEST_CASTER_CLUMP_CENTROID
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 141
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY,  TARGET_CHECK_ENTRY,    TARGET_DIR_FRONT_RIGHT}, // 142 TARGET_DEST_NEARBY_ENTRY_OR_DB
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 143
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 144
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 145
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 146
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 147
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_DEST,   TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 148 TARGET_DEST_DEST_TARGET_TOWARDS_CASTER
    {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_RANDOM},      // 149
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 150 TARGET_UNIT_OWN_CRITTER
    {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_AREA,    TARGET_CHECK_ENEMY,    TARGET_DIR_NONE},        // 151
    {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE,   TARGET_SELECT_CATEGORY_NYI,     TARGET_CHECK_DEFAULT,  TARGET_DIR_NONE},        // 152
} };

SpellEffectInfo::SpellEffectInfo(): _spellInfo(nullptr), EffectIndex(EFFECT_0), Effect(SPELL_EFFECT_NONE), ApplyAuraName(AuraType(0)), ApplyAuraPeriod(0),
    BasePoints(0), RealPointsPerLevel(0), PointsPerResource(0), Amplitude(0), ChainAmplitude(0),
    BonusCoefficient(0), MiscValue(0), MiscValueB(0), Mechanic(MECHANIC_NONE), PositionFacing(0),
    TargetARadiusEntry(nullptr), TargetBRadiusEntry(nullptr), ChainTargets(0), ItemType(0), TriggerSpell(0),
    BonusCoefficientFromAP(0.0f), ImplicitTargetConditions(nullptr),
    EffectAttributes(SpellEffectAttributes::None), Scaling(), _immunityInfo(nullptr)
{
}

SpellEffectInfo::SpellEffectInfo(SpellInfo const* spellInfo, SpellEffectEntry const& _effect)
    : EffectAttributes(SpellEffectAttributes::None)
{
    ASSERT(spellInfo);

    _spellInfo = spellInfo;
    EffectIndex = SpellEffIndex(_effect.EffectIndex);
    Effect = SpellEffectName(_effect.Effect);
    ApplyAuraName = AuraType(_effect.EffectAura);
    ApplyAuraPeriod = _effect.EffectAuraPeriod;
    BasePoints = _effect.EffectBasePoints;
    RealPointsPerLevel = _effect.EffectRealPointsPerLevel;
    PointsPerResource = _effect.EffectPointsPerResource;
    Amplitude = _effect.EffectAmplitude;
    ChainAmplitude = _effect.EffectChainAmplitude;
    BonusCoefficient = _effect.EffectBonusCoefficient;
    MiscValue = _effect.EffectMiscValue[0];
    MiscValueB = _effect.EffectMiscValue[1];
    Mechanic = Mechanics(_effect.EffectMechanic);
    PositionFacing = _effect.EffectPosFacing;
    TargetA = SpellImplicitTargetInfo(_effect.ImplicitTarget[0]);
    TargetB = SpellImplicitTargetInfo(_effect.ImplicitTarget[1]);
    TargetARadiusEntry = sSpellRadiusStore.LookupEntry(_effect.EffectRadiusIndex[0]);
    TargetBRadiusEntry = sSpellRadiusStore.LookupEntry(_effect.EffectRadiusIndex[1]);
    ChainTargets = _effect.EffectChainTargets;
    ItemType = _effect.EffectItemType;
    TriggerSpell = _effect.EffectTriggerSpell;
    SpellClassMask = _effect.EffectSpellClassMask;
    BonusCoefficientFromAP = _effect.BonusCoefficientFromAP;
    Scaling.Class = _effect.ScalingClass;
    Scaling.Coefficient = _effect.Coefficient;
    Scaling.Variance = _effect.Variance;
    Scaling.ResourceCoefficient = _effect.ResourceCoefficient;
    ImplicitTargetConditions = nullptr;
    EffectAttributes = _effect.GetEffectAttributes();
    _immunityInfo = nullptr;
}

SpellEffectInfo::SpellEffectInfo(SpellEffectInfo&&) noexcept = default;
SpellEffectInfo& SpellEffectInfo::operator=(SpellEffectInfo&&) noexcept = default;

SpellEffectInfo::~SpellEffectInfo() = default;

bool SpellEffectInfo::IsEffect() const
{
    return Effect != 0;
}

bool SpellEffectInfo::IsEffect(SpellEffectName effectName) const
{
    return Effect == effectName;
}

bool SpellEffectInfo::IsAura() const
{
    return (IsUnitOwnedAuraEffect() || Effect == SPELL_EFFECT_PERSISTENT_AREA_AURA) && ApplyAuraName != 0;
}

bool SpellEffectInfo::IsAura(AuraType aura) const
{
    return IsAura() && ApplyAuraName == uint32(aura);
}

bool SpellEffectInfo::IsTargetingArea() const
{
    return TargetA.IsArea() || TargetB.IsArea();
}

bool SpellEffectInfo::IsAreaAuraEffect() const
{
    if (Effect == SPELL_EFFECT_APPLY_AREA_AURA_PARTY    ||
        Effect == SPELL_EFFECT_APPLY_AREA_AURA_RAID     ||
        Effect == SPELL_EFFECT_APPLY_AREA_AURA_FRIEND   ||
        Effect == SPELL_EFFECT_APPLY_AREA_AURA_ENEMY    ||
        Effect == SPELL_EFFECT_APPLY_AREA_AURA_PET      ||
        Effect == SPELL_EFFECT_APPLY_AREA_AURA_OWNER    ||
        Effect == SPELL_EFFECT_APPLY_AREA_AURA_SUMMONS  ||
        Effect == SPELL_EFFECT_APPLY_AREA_AURA_PARTY_NONRANDOM)
        return true;
    return false;
}

bool SpellEffectInfo::IsUnitOwnedAuraEffect() const
{
    return IsAreaAuraEffect() || Effect == SPELL_EFFECT_APPLY_AURA || Effect == SPELL_EFFECT_APPLY_AURA_ON_PET;
}

int32 SpellEffectInfo::CalcValue(WorldObject const* caster /*= nullptr*/, int32 const* bp /*= nullptr*/, Unit const* target /*= nullptr*/, float* variance /*= nullptr*/, uint32 castItemId /*= 0*/, int32 itemLevel /*= -1*/) const
{
    double basePointsPerLevel = RealPointsPerLevel;
    // TODO: this needs to be a float, not rounded
    int32 basePoints = CalcBaseValue(caster, target, castItemId, itemLevel);
    double value = bp ? *bp : basePoints;
    double comboDamage = PointsPerResource;

    Unit const* casterUnit = nullptr;
    if (caster)
        casterUnit = caster->ToUnit();

    if (Scaling.Variance)
    {
        float delta = fabs(Scaling.Variance * 0.5f);
        double valueVariance = frand(-delta, delta);
        value += double(basePoints) * valueVariance;

        if (variance)
            *variance = valueVariance;
    }

    // base amount modification based on spell lvl vs caster lvl
    if (Scaling.Coefficient != 0.0f)
    {
        if (Scaling.ResourceCoefficient)
            comboDamage = Scaling.ResourceCoefficient * value;
    }
    else if (GetScalingExpectedStat() == ExpectedStatType::None)
    {
        if (casterUnit && basePointsPerLevel != 0.0)
        {
            int32 level = int32(casterUnit->GetLevel());
            if (level > int32(_spellInfo->MaxLevel) && _spellInfo->MaxLevel > 0)
                level = int32(_spellInfo->MaxLevel);

            // if base level is greater than spell level, reduce by base level (eg. pilgrims foods)
            level -= int32(std::max(_spellInfo->BaseLevel, _spellInfo->SpellLevel));
            if (level < 0)
                level = 0;
            value += level * basePointsPerLevel;
        }
    }

    // random damage
    if (casterUnit)
    {
        // bonus amount from combo points
        if (comboDamage)
            if (int32 comboPoints = casterUnit->GetPower(POWER_COMBO_POINTS))
                value += comboDamage * comboPoints;
    }

    if (caster)
        value = caster->ApplyEffectModifiers(_spellInfo, EffectIndex, value);

    return int32(round(value));
}

int32 SpellEffectInfo::CalcBaseValue(WorldObject const* caster, Unit const* target, uint32 itemId, int32 itemLevel) const
{
    if (Scaling.Coefficient != 0.0f)
    {
        uint32 level = _spellInfo->SpellLevel;
        if (target && _spellInfo->HasAttribute(SPELL_ATTR8_USE_TARGETS_LEVEL_FOR_SPELL_SCALING))
            level = target->GetLevel();
        else if (caster && caster->IsUnit())
            level = caster->ToUnit()->GetLevel();

        if (_spellInfo->BaseLevel && !_spellInfo->HasAttribute(SPELL_ATTR11_SCALES_WITH_ITEM_LEVEL) && _spellInfo->HasAttribute(SPELL_ATTR10_USE_SPELL_BASE_LEVEL_FOR_SCALING))
            level = _spellInfo->BaseLevel;

        if (_spellInfo->Scaling.MinScalingLevel && _spellInfo->Scaling.MinScalingLevel > level)
            level = _spellInfo->Scaling.MinScalingLevel;

        if (_spellInfo->Scaling.MaxScalingLevel && _spellInfo->Scaling.MaxScalingLevel < level)
            level = _spellInfo->Scaling.MaxScalingLevel;

        float value = 0.0f;
        if (level > 0)
        {
            if (!Scaling.Class)
                return 0;

            uint32 effectiveItemLevel = itemLevel != -1 ? uint32(itemLevel) : 1u;
            if (_spellInfo->Scaling.ScalesFromItemLevel || _spellInfo->HasAttribute(SPELL_ATTR11_SCALES_WITH_ITEM_LEVEL))
            {
                if (_spellInfo->Scaling.ScalesFromItemLevel)
                    effectiveItemLevel = _spellInfo->Scaling.ScalesFromItemLevel;

                if (Scaling.Class == -8 || Scaling.Class == -9)
                {
                    RandPropPointsEntry const* randPropPoints = sRandPropPointsStore.LookupEntry(effectiveItemLevel);
                    if (!randPropPoints)
                        randPropPoints = sRandPropPointsStore.AssertEntry(sRandPropPointsStore.GetNumRows() - 1);

                    value = Scaling.Class == -8 ? randPropPoints->DamageReplaceStatF : randPropPoints->DamageSecondaryF;
                }
                else
                    value = GetRandomPropertyPoints(effectiveItemLevel, ITEM_QUALITY_RARE, INVTYPE_CHEST, 0);
            }
            else
                value = GetSpellScalingColumnForClass(sSpellScalingGameTable.GetRow(level), Scaling.Class);

            if (Scaling.Class == -7)
                if (GtCombatRatingsMultByILvl const* ratingMult = sCombatRatingsMultByILvlGameTable.GetRow(effectiveItemLevel))
                    if (ItemSparseEntry const* itemSparse = sItemSparseStore.LookupEntry(itemId))
                        value *= GetIlvlStatMultiplier(ratingMult, InventoryType(itemSparse->InventoryType));

            if (Scaling.Class == -6)
                if (GtStaminaMultByILvl const* staminaMult = sStaminaMultByILvlGameTable.GetRow(effectiveItemLevel))
                    if (ItemSparseEntry const* itemSparse = sItemSparseStore.LookupEntry(itemId))
                        value *= GetIlvlStatMultiplier(staminaMult, InventoryType(itemSparse->InventoryType));
        }

        value *= Scaling.Coefficient;
        if (value > 0.0f && value < 1.0f)
            value = 1.0f;

        return int32(round(value));
    }
    else
    {
        float value = BasePoints;
        ExpectedStatType stat = GetScalingExpectedStat();
        if (stat != ExpectedStatType::None)
        {
            if (_spellInfo->HasAttribute(SPELL_ATTR0_SCALES_WITH_CREATURE_LEVEL))
                stat = ExpectedStatType::CreatureAutoAttackDps;

            // TODO - add expansion and content tuning id args?
            uint32 contentTuningId = _spellInfo->ContentTuningId; // content tuning should be passed as arg, the one stored in SpellInfo is fallback
            int32 expansion = -2;
            if (ContentTuningEntry const* contentTuning = sContentTuningStore.LookupEntry(contentTuningId))
                expansion = contentTuning->ExpansionID;

            int32 level = 1;
            if (target && _spellInfo->HasAttribute(SPELL_ATTR8_USE_TARGETS_LEVEL_FOR_SPELL_SCALING))
                level = target->GetLevel();
            else if (caster && caster->IsUnit())
                level = caster->ToUnit()->GetLevel();

            value = sDB2Manager.EvaluateExpectedStat(stat, level, expansion, 0, CLASS_NONE, 0) * BasePoints / 100.0f;
        }

        return int32(round(value));
    }
}

float SpellEffectInfo::CalcValueMultiplier(WorldObject* caster, Spell* spell /*= nullptr*/) const
{
    float multiplier = Amplitude;
    if (Player* modOwner = (caster ? caster->GetSpellModOwner() : nullptr))
        modOwner->ApplySpellMod(_spellInfo, SpellModOp::Amplitude, multiplier, spell);
    return multiplier;
}

float SpellEffectInfo::CalcDamageMultiplier(WorldObject* caster, Spell* spell /*= nullptr*/) const
{
    float multiplierPercent = ChainAmplitude * 100.0f;
    if (Player* modOwner = (caster ? caster->GetSpellModOwner() : nullptr))
        modOwner->ApplySpellMod(_spellInfo, SpellModOp::ChainAmplitude, multiplierPercent, spell);
    return multiplierPercent / 100.0f;
}

bool SpellEffectInfo::HasRadius(SpellTargetIndex targetIndex) const
{
    switch (targetIndex)
    {
        case SpellTargetIndex::TargetA:
            return TargetARadiusEntry != nullptr;
        case SpellTargetIndex::TargetB:
            return TargetBRadiusEntry != nullptr;
        default:
            return false;
    }
}

float SpellEffectInfo::CalcRadius(WorldObject* caster /*= nullptr*/, SpellTargetIndex targetIndex /*=SpellTargetIndex::TargetA*/, Spell* spell /*= nullptr*/) const
{
    // TargetA -> TargetARadiusEntry
    // TargetB -> TargetBRadiusEntry
    // Aura effects have TargetARadiusEntry == TargetBRadiusEntry (mostly)
    SpellImplicitTargetInfo target = TargetA;
    SpellRadiusEntry const* entry = TargetARadiusEntry;
    if (targetIndex == SpellTargetIndex::TargetB && HasRadius(targetIndex))
    {
        target = TargetB;
        entry = TargetBRadiusEntry;
    }

    if (!entry)
        return 0.0f;

    float radius = entry->RadiusMin;

    // Random targets use random value between RadiusMin and RadiusMax
    // For other cases, client uses RadiusMax if RadiusMin is 0
    if (target.GetTarget() == TARGET_DEST_CASTER_RANDOM ||
        target.GetTarget() == TARGET_DEST_TARGET_RANDOM ||
        target.GetTarget() == TARGET_DEST_DEST_RANDOM)
        radius += (entry->RadiusMax - radius) * rand_norm();
    else if (radius == 0.0f)
        radius = entry->RadiusMax;

    if (caster)
    {
        if (Unit const* casterUnit = caster->ToUnit())
            radius += entry->RadiusPerLevel * casterUnit->GetLevel();

        radius = std::min(radius, entry->RadiusMax);

        if (Player* modOwner = caster->GetSpellModOwner())
            modOwner->ApplySpellMod(_spellInfo, SpellModOp::Radius, radius, spell);

        if (!_spellInfo->HasAttribute(SPELL_ATTR9_NO_MOVEMENT_RADIUS_BONUS))
            if (Unit const* casterUnit = caster->ToUnit(); casterUnit && Spell::CanIncreaseRangeByMovement(casterUnit))
                radius += 2.0f;
    }

    return radius;
}

uint32 SpellEffectInfo::GetProvidedTargetMask() const
{
    return GetTargetFlagMask(TargetA.GetObjectType()) | GetTargetFlagMask(TargetB.GetObjectType());
}

uint32 SpellEffectInfo::GetMissingTargetMask(bool srcSet /*= false*/, bool dstSet /*= false*/, uint32 mask /*=0*/) const
{
    uint32 effImplicitTargetMask = GetTargetFlagMask(GetUsedTargetObjectType());
    uint32 providedTargetMask = GetProvidedTargetMask() | mask;

    // remove all flags covered by effect target mask
    if (providedTargetMask & TARGET_FLAG_UNIT_MASK)
        effImplicitTargetMask &= ~(TARGET_FLAG_UNIT_MASK);
    if (providedTargetMask & TARGET_FLAG_CORPSE_MASK)
        effImplicitTargetMask &= ~(TARGET_FLAG_UNIT_MASK | TARGET_FLAG_CORPSE_MASK);
    if (providedTargetMask & TARGET_FLAG_GAMEOBJECT_ITEM)
        effImplicitTargetMask &= ~(TARGET_FLAG_GAMEOBJECT_ITEM | TARGET_FLAG_GAMEOBJECT | TARGET_FLAG_ITEM);
    if (providedTargetMask & TARGET_FLAG_GAMEOBJECT)
        effImplicitTargetMask &= ~(TARGET_FLAG_GAMEOBJECT | TARGET_FLAG_GAMEOBJECT_ITEM);
    if (providedTargetMask & TARGET_FLAG_ITEM)
        effImplicitTargetMask &= ~(TARGET_FLAG_ITEM | TARGET_FLAG_GAMEOBJECT_ITEM);
    if (dstSet || providedTargetMask & TARGET_FLAG_DEST_LOCATION)
        effImplicitTargetMask &= ~(TARGET_FLAG_DEST_LOCATION);
    if (srcSet || providedTargetMask & TARGET_FLAG_SOURCE_LOCATION)
        effImplicitTargetMask &= ~(TARGET_FLAG_SOURCE_LOCATION);

    return effImplicitTargetMask;
}

SpellEffectImplicitTargetTypes SpellEffectInfo::GetImplicitTargetType() const
{
    return _data[Effect].ImplicitTargetType;
}

SpellTargetObjectTypes SpellEffectInfo::GetUsedTargetObjectType() const
{
    return _data[Effect].UsedTargetObjectType;
}

ExpectedStatType SpellEffectInfo::GetScalingExpectedStat() const
{
    switch (Effect)
    {
        case SPELL_EFFECT_SCHOOL_DAMAGE:
        case SPELL_EFFECT_ENVIRONMENTAL_DAMAGE:
        case SPELL_EFFECT_HEALTH_LEECH:
        case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL:
        case SPELL_EFFECT_WEAPON_DAMAGE:
            return ExpectedStatType::CreatureSpellDamage;
        case SPELL_EFFECT_HEAL:
        case SPELL_EFFECT_HEAL_MECHANICAL:
            return ExpectedStatType::PlayerHealth;
        case SPELL_EFFECT_ENERGIZE:
        case SPELL_EFFECT_POWER_BURN:
            if (MiscValue == POWER_MANA)
                return ExpectedStatType::PlayerMana;
            return ExpectedStatType::None;
        case SPELL_EFFECT_POWER_DRAIN:
            return ExpectedStatType::PlayerMana;
        case SPELL_EFFECT_APPLY_AURA:
        case SPELL_EFFECT_PERSISTENT_AREA_AURA:
        case SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
        case SPELL_EFFECT_APPLY_AREA_AURA_RAID:
        case SPELL_EFFECT_APPLY_AREA_AURA_PET:
        case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
        case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
        case SPELL_EFFECT_APPLY_AREA_AURA_OWNER:
        case SPELL_EFFECT_APPLY_AURA_ON_PET:
        case SPELL_EFFECT_APPLY_AREA_AURA_SUMMONS:
        case SPELL_EFFECT_APPLY_AREA_AURA_PARTY_NONRANDOM:
            switch (ApplyAuraName)
            {
                case SPELL_AURA_PERIODIC_DAMAGE:
                case SPELL_AURA_MOD_DAMAGE_DONE:
                case SPELL_AURA_DAMAGE_SHIELD:
                case SPELL_AURA_PROC_TRIGGER_DAMAGE:
                case SPELL_AURA_PERIODIC_LEECH:
                case SPELL_AURA_MOD_DAMAGE_DONE_CREATURE:
                case SPELL_AURA_PERIODIC_HEALTH_FUNNEL:
                case SPELL_AURA_MOD_MELEE_ATTACK_POWER_VERSUS:
                case SPELL_AURA_MOD_RANGED_ATTACK_POWER_VERSUS:
                case SPELL_AURA_MOD_FLAT_SPELL_DAMAGE_VERSUS:
                    return ExpectedStatType::CreatureSpellDamage;
                case SPELL_AURA_PERIODIC_HEAL:
                case SPELL_AURA_MOD_DAMAGE_TAKEN:
                case SPELL_AURA_MOD_INCREASE_HEALTH:
                case SPELL_AURA_SCHOOL_ABSORB:
                case SPELL_AURA_MOD_REGEN:
                case SPELL_AURA_MANA_SHIELD:
                case SPELL_AURA_MOD_HEALING:
                case SPELL_AURA_MOD_HEALING_DONE:
                case SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT:
                case SPELL_AURA_MOD_MAX_HEALTH:
                case SPELL_AURA_MOD_INCREASE_HEALTH_2:
                case SPELL_AURA_SCHOOL_HEAL_ABSORB:
                    return ExpectedStatType::PlayerHealth;
                case SPELL_AURA_PERIODIC_MANA_LEECH:
                    return ExpectedStatType::PlayerMana;
                case SPELL_AURA_MOD_STAT:
                case SPELL_AURA_MOD_ATTACK_POWER:
                case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
                    return ExpectedStatType::PlayerPrimaryStat;
                case SPELL_AURA_MOD_RATING:
                    return ExpectedStatType::PlayerSecondaryStat;
                case SPELL_AURA_MOD_RESISTANCE:
                case SPELL_AURA_MOD_BASE_RESISTANCE:
                case SPELL_AURA_MOD_TARGET_RESISTANCE:
                case SPELL_AURA_MOD_BONUS_ARMOR:
                    return ExpectedStatType::ArmorConstant;
                case SPELL_AURA_PERIODIC_ENERGIZE:
                case SPELL_AURA_MOD_INCREASE_ENERGY:
                case SPELL_AURA_MOD_POWER_COST_SCHOOL:
                case SPELL_AURA_MOD_POWER_REGEN:
                case SPELL_AURA_POWER_BURN:
                case SPELL_AURA_MOD_MAX_POWER:
                    if (MiscValue == POWER_MANA)
                        return ExpectedStatType::PlayerMana;
                    return ExpectedStatType::None;
                default:
                    break;
            }
            break;
        default:
            break;
    }

    return ExpectedStatType::None;
}

std::array<SpellEffectInfo::StaticData, TOTAL_SPELL_EFFECTS> SpellEffectInfo::_data =
{ {
    // implicit target type           used target object type
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 0
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 1 SPELL_EFFECT_INSTAKILL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 2 SPELL_EFFECT_SCHOOL_DAMAGE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 3 SPELL_EFFECT_DUMMY
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 4 SPELL_EFFECT_PORTAL_TELEPORT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 5 SPELL_EFFECT_5
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 6 SPELL_EFFECT_APPLY_AURA
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 7 SPELL_EFFECT_ENVIRONMENTAL_DAMAGE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 8 SPELL_EFFECT_POWER_DRAIN
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 9 SPELL_EFFECT_HEALTH_LEECH
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 10 SPELL_EFFECT_HEAL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 11 SPELL_EFFECT_BIND
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 12 SPELL_EFFECT_PORTAL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 13 SPELL_EFFECT_TELEPORT_TO_RETURN_POINT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 14 SPELL_EFFECT_INCREASE_CURRENCY_CAP
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 15 SPELL_EFFECT_TELEPORT_WITH_SPELL_VISUAL_KIT_LOADING_SCREEN
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 16 SPELL_EFFECT_QUEST_COMPLETE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 17 SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_CORPSE_ALLY}, // 18 SPELL_EFFECT_RESURRECT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 19 SPELL_EFFECT_ADD_EXTRA_ATTACKS
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 20 SPELL_EFFECT_DODGE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 21 SPELL_EFFECT_EVADE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 22 SPELL_EFFECT_PARRY
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 23 SPELL_EFFECT_BLOCK
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 24 SPELL_EFFECT_CREATE_ITEM
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 25 SPELL_EFFECT_WEAPON
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 26 SPELL_EFFECT_DEFENSE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 27 SPELL_EFFECT_PERSISTENT_AREA_AURA
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 28 SPELL_EFFECT_SUMMON
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 29 SPELL_EFFECT_LEAP
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 30 SPELL_EFFECT_ENERGIZE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 31 SPELL_EFFECT_WEAPON_PERCENT_DAMAGE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 32 SPELL_EFFECT_TRIGGER_MISSILE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_GOBJ_ITEM}, // 33 SPELL_EFFECT_OPEN_LOCK
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 34 SPELL_EFFECT_SUMMON_CHANGE_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 35 SPELL_EFFECT_APPLY_AREA_AURA_PARTY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 36 SPELL_EFFECT_LEARN_SPELL
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 37 SPELL_EFFECT_SPELL_DEFENSE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 38 SPELL_EFFECT_DISPEL
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 39 SPELL_EFFECT_LANGUAGE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 40 SPELL_EFFECT_DUAL_WIELD
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 41 SPELL_EFFECT_JUMP
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 42 SPELL_EFFECT_JUMP_DEST
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 43 SPELL_EFFECT_TELEPORT_UNITS_FACE_CASTER
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 44 SPELL_EFFECT_SKILL_STEP
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 45 SPELL_EFFECT_ADD_HONOR
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 46 SPELL_EFFECT_SPAWN
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 47 SPELL_EFFECT_TRADE_SKILL
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 48 SPELL_EFFECT_STEALTH
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 49 SPELL_EFFECT_DETECT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 50 SPELL_EFFECT_TRANS_DOOR
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 51 SPELL_EFFECT_FORCE_CRITICAL_HIT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 52 SPELL_EFFECT_SET_MAX_BATTLE_PET_COUNT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 53 SPELL_EFFECT_ENCHANT_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 54 SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 55 SPELL_EFFECT_TAMECREATURE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 56 SPELL_EFFECT_SUMMON_PET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 57 SPELL_EFFECT_LEARN_PET_SPELL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 58 SPELL_EFFECT_WEAPON_DAMAGE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 59 SPELL_EFFECT_CREATE_RANDOM_ITEM
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 60 SPELL_EFFECT_PROFICIENCY
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 61 SPELL_EFFECT_SEND_EVENT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 62 SPELL_EFFECT_POWER_BURN
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 63 SPELL_EFFECT_THREAT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 64 SPELL_EFFECT_TRIGGER_SPELL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 65 SPELL_EFFECT_APPLY_AREA_AURA_RAID
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 66 SPELL_EFFECT_RECHARGE_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 67 SPELL_EFFECT_HEAL_MAX_HEALTH
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 68 SPELL_EFFECT_INTERRUPT_CAST
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 69 SPELL_EFFECT_DISTRACT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 70 SPELL_EFFECT_COMPLETE_AND_REWARD_WORLD_QUEST
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 71 SPELL_EFFECT_PICKPOCKET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 72 SPELL_EFFECT_ADD_FARSIGHT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 73 SPELL_EFFECT_UNTRAIN_TALENTS
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 74 SPELL_EFFECT_APPLY_GLYPH
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 75 SPELL_EFFECT_HEAL_MECHANICAL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 76 SPELL_EFFECT_SUMMON_OBJECT_WILD
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 77 SPELL_EFFECT_SCRIPT_EFFECT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 78 SPELL_EFFECT_ATTACK
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 79 SPELL_EFFECT_SANCTUARY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 80 SPELL_EFFECT_MODIFY_FOLLOWER_ITEM_LEVEL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 81 SPELL_EFFECT_PUSH_ABILITY_TO_ACTION_BAR
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 82 SPELL_EFFECT_BIND_SIGHT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 83 SPELL_EFFECT_DUEL
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 84 SPELL_EFFECT_STUCK
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 85 SPELL_EFFECT_SUMMON_PLAYER
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_GOBJ}, // 86 SPELL_EFFECT_ACTIVATE_OBJECT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_GOBJ}, // 87 SPELL_EFFECT_GAMEOBJECT_DAMAGE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_GOBJ}, // 88 SPELL_EFFECT_GAMEOBJECT_REPAIR
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_GOBJ}, // 89 SPELL_EFFECT_GAMEOBJECT_SET_DESTRUCTION_STATE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 90 SPELL_EFFECT_KILL_CREDIT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 91 SPELL_EFFECT_THREAT_ALL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 92 SPELL_EFFECT_ENCHANT_HELD_ITEM
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 93 SPELL_EFFECT_FORCE_DESELECT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 94 SPELL_EFFECT_SELF_RESURRECT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 95 SPELL_EFFECT_SKINNING
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 96 SPELL_EFFECT_CHARGE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 97 SPELL_EFFECT_CAST_BUTTON
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 98 SPELL_EFFECT_KNOCK_BACK
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 99 SPELL_EFFECT_DISENCHANT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 100 SPELL_EFFECT_INEBRIATE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 101 SPELL_EFFECT_FEED_PET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 102 SPELL_EFFECT_DISMISS_PET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 103 SPELL_EFFECT_REPUTATION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 104 SPELL_EFFECT_SUMMON_OBJECT_SLOT1
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 105 SPELL_EFFECT_SURVEY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 106 SPELL_EFFECT_CHANGE_RAID_MARKER
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 107 SPELL_EFFECT_SHOW_CORPSE_LOOT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 108 SPELL_EFFECT_DISPEL_MECHANIC
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 109 SPELL_EFFECT_RESURRECT_PET
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 110 SPELL_EFFECT_DESTROY_ALL_TOTEMS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 111 SPELL_EFFECT_DURABILITY_DAMAGE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 112 SPELL_EFFECT_112
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 113 SPELL_EFFECT_CANCEL_CONVERSATION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 114 SPELL_EFFECT_ATTACK_ME
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 115 SPELL_EFFECT_DURABILITY_DAMAGE_PCT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_CORPSE_ENEMY}, // 116 SPELL_EFFECT_SKIN_PLAYER_CORPSE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 117 SPELL_EFFECT_SPIRIT_HEAL
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 118 SPELL_EFFECT_SKILL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 119 SPELL_EFFECT_APPLY_AREA_AURA_PET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 120 SPELL_EFFECT_TELEPORT_GRAVEYARD
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 121 SPELL_EFFECT_NORMALIZED_WEAPON_DMG
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 122 SPELL_EFFECT_122
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 123 SPELL_EFFECT_SEND_TAXI
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 124 SPELL_EFFECT_PULL_TOWARDS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 125 SPELL_EFFECT_MODIFY_THREAT_PERCENT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 126 SPELL_EFFECT_STEAL_BENEFICIAL_BUFF
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 127 SPELL_EFFECT_PROSPECTING
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 128 SPELL_EFFECT_APPLY_AREA_AURA_FRIEND
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 129 SPELL_EFFECT_APPLY_AREA_AURA_ENEMY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 130 SPELL_EFFECT_REDIRECT_THREAT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 131 SPELL_EFFECT_PLAY_SOUND
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 132 SPELL_EFFECT_PLAY_MUSIC
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 133 SPELL_EFFECT_UNLEARN_SPECIALIZATION
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 134 SPELL_EFFECT_KILL_CREDIT2
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 135 SPELL_EFFECT_CALL_PET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 136 SPELL_EFFECT_HEAL_PCT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 137 SPELL_EFFECT_ENERGIZE_PCT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 138 SPELL_EFFECT_LEAP_BACK
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 139 SPELL_EFFECT_CLEAR_QUEST
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 140 SPELL_EFFECT_FORCE_CAST
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 141 SPELL_EFFECT_FORCE_CAST_WITH_VALUE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 142 SPELL_EFFECT_TRIGGER_SPELL_WITH_VALUE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 143 SPELL_EFFECT_APPLY_AREA_AURA_OWNER
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 144 SPELL_EFFECT_KNOCK_BACK_DEST
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 145 SPELL_EFFECT_PULL_TOWARDS_DEST
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 146 SPELL_EFFECT_RESTORE_GARRISON_TROOP_VITALITY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 147 SPELL_EFFECT_QUEST_FAIL
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 148 SPELL_EFFECT_TRIGGER_MISSILE_SPELL_WITH_VALUE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 149 SPELL_EFFECT_CHARGE_DEST
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 150 SPELL_EFFECT_QUEST_START
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 151 SPELL_EFFECT_TRIGGER_SPELL_2
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 152 SPELL_EFFECT_SUMMON_RAF_FRIEND
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 153 SPELL_EFFECT_CREATE_TAMED_PET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 154 SPELL_EFFECT_DISCOVER_TAXI
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_UNIT}, // 155 SPELL_EFFECT_TITAN_GRIP
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 156 SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 157 SPELL_EFFECT_CREATE_LOOT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 158 SPELL_EFFECT_MILLING
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 159 SPELL_EFFECT_ALLOW_RENAME_PET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 160 SPELL_EFFECT_FORCE_CAST_2
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 161 SPELL_EFFECT_TALENT_SPEC_COUNT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 162 SPELL_EFFECT_TALENT_SPEC_SELECT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 163 SPELL_EFFECT_OBLITERATE_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 164 SPELL_EFFECT_REMOVE_AURA
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 165 SPELL_EFFECT_DAMAGE_FROM_MAX_HEALTH_PCT
    {EFFECT_IMPLICIT_TARGET_CASTER,   TARGET_OBJECT_TYPE_UNIT}, // 166 SPELL_EFFECT_GIVE_CURRENCY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 167 SPELL_EFFECT_UPDATE_PLAYER_PHASE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 168 SPELL_EFFECT_ALLOW_CONTROL_PET
    {EFFECT_IMPLICIT_TARGET_CASTER,   TARGET_OBJECT_TYPE_UNIT}, // 169 SPELL_EFFECT_DESTROY_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 170 SPELL_EFFECT_UPDATE_ZONE_AURAS_AND_PHASES
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 171 SPELL_EFFECT_SUMMON_PERSONAL_GAMEOBJECT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_CORPSE_ALLY}, // 172 SPELL_EFFECT_RESURRECT_WITH_AURA
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 173 SPELL_EFFECT_UNLOCK_GUILD_VAULT_TAB
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 174 SPELL_EFFECT_APPLY_AURA_ON_PET
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 175 SPELL_EFFECT_175
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 176 SPELL_EFFECT_SANCTUARY_2
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 177 SPELL_EFFECT_DESPAWN_PERSISTENT_AREA_AURA
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 178 SPELL_EFFECT_178
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 179 SPELL_EFFECT_CREATE_AREATRIGGER
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 180 SPELL_EFFECT_UPDATE_AREATRIGGER
    {EFFECT_IMPLICIT_TARGET_CASTER,   TARGET_OBJECT_TYPE_UNIT}, // 181 SPELL_EFFECT_REMOVE_TALENT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 182 SPELL_EFFECT_DESPAWN_AREATRIGGER
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 183 SPELL_EFFECT_183
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 184 SPELL_EFFECT_REPUTATION_2
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 185 SPELL_EFFECT_185
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 186 SPELL_EFFECT_186
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 187 SPELL_EFFECT_RANDOMIZE_ARCHAEOLOGY_DIGSITES
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_DEST}, // 188 SPELL_EFFECT_SUMMON_STABLED_PET_AS_GUARDIAN
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 189 SPELL_EFFECT_LOOT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 190 SPELL_EFFECT_CHANGE_PARTY_MEMBERS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 191 SPELL_EFFECT_TELEPORT_TO_DIGSITE
    {EFFECT_IMPLICIT_TARGET_CASTER,   TARGET_OBJECT_TYPE_UNIT}, // 192 SPELL_EFFECT_UNCAGE_BATTLEPET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 193 SPELL_EFFECT_START_PET_BATTLE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 194 SPELL_EFFECT_194
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 195 SPELL_EFFECT_PLAY_SCENE_SCRIPT_PACKAGE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 196 SPELL_EFFECT_CREATE_SCENE_OBJECT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 197 SPELL_EFFECT_CREATE_PERSONAL_SCENE_OBJECT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 198 SPELL_EFFECT_PLAY_SCENE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 199 SPELL_EFFECT_DESPAWN_SUMMON
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 200 SPELL_EFFECT_HEAL_BATTLEPET_PCT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 201 SPELL_EFFECT_ENABLE_BATTLE_PETS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 202 SPELL_EFFECT_APPLY_AREA_AURA_SUMMONS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 203 SPELL_EFFECT_REMOVE_AURA_2
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 204 SPELL_EFFECT_CHANGE_BATTLEPET_QUALITY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 205 SPELL_EFFECT_LAUNCH_QUEST_CHOICE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 206 SPELL_EFFECT_ALTER_IETM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 207 SPELL_EFFECT_LAUNCH_QUEST_TASK
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 208 SPELL_EFFECT_SET_REPUTATION
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 209 SPELL_EFFECT_209
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 210 SPELL_EFFECT_LEARN_GARRISON_BUILDING
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 211 SPELL_EFFECT_LEARN_GARRISON_SPECIALIZATION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 212 SPELL_EFFECT_REMOVE_AURA_BY_SPELL_LABEL
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 213 SPELL_EFFECT_JUMP_DEST_2
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 214 SPELL_EFFECT_CREATE_GARRISON
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 215 SPELL_EFFECT_UPGRADE_CHARACTER_SPELLS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 216 SPELL_EFFECT_CREATE_SHIPMENT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 217 SPELL_EFFECT_UPGRADE_GARRISON
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 218 SPELL_EFFECT_218
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 219 SPELL_EFFECT_CREATE_CONVERSATION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 220 SPELL_EFFECT_ADD_GARRISON_FOLLOWER
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 221 SPELL_EFFECT_ADD_GARRISON_MISSION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 222 SPELL_EFFECT_CREATE_HEIRLOOM_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 223 SPELL_EFFECT_CHANGE_ITEM_BONUSES
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 224 SPELL_EFFECT_ACTIVATE_GARRISON_BUILDING
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 225 SPELL_EFFECT_GRANT_BATTLEPET_LEVEL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 226 SPELL_EFFECT_TRIGGER_ACTION_SET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 227 SPELL_EFFECT_TELEPORT_TO_LFG_DUNGEON
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 228 SPELL_EFFECT_228
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 229 SPELL_EFFECT_SET_FOLLOWER_QUALITY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 230 SPELL_EFFECT_230
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 231 SPELL_EFFECT_INCREASE_FOLLOWER_EXPERIENCE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 232 SPELL_EFFECT_REMOVE_PHASE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 233 SPELL_EFFECT_RANDOMIZE_FOLLOWER_ABILITIES
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 234 SPELL_EFFECT_234
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 235 SPELL_EFFECT_235
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 236 SPELL_EFFECT_GIVE_EXPERIENCE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 237 SPELL_EFFECT_GIVE_RESTED_EXPERIENCE_BONUS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 238 SPELL_EFFECT_INCREASE_SKILL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 239 SPELL_EFFECT_END_GARRISON_BUILDING_CONSTRUCTION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 240 SPELL_EFFECT_GIVE_ARTIFACT_POWER
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 241 SPELL_EFFECT_241
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 242 SPELL_EFFECT_GIVE_ARTIFACT_POWER_NO_BONUS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 243 SPELL_EFFECT_APPLY_ENCHANT_ILLUSION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 244 SPELL_EFFECT_LEARN_FOLLOWER_ABILITY
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 245 SPELL_EFFECT_UPGRADE_HEIRLOOM
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 246 SPELL_EFFECT_FINISH_GARRISON_MISSION
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 247 SPELL_EFFECT_ADD_GARRISON_MISSION_SET
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 248 SPELL_EFFECT_FINISH_SHIPMENT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 249 SPELL_EFFECT_FORCE_EQUIP_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 250 SPELL_EFFECT_TAKE_SCREENSHOT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 251 SPELL_EFFECT_SET_GARRISON_CACHE_SIZE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT_AND_DEST}, // 252 SPELL_EFFECT_TELEPORT_UNITS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 253 SPELL_EFFECT_GIVE_HONOR
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_DEST}, // 254 SPELL_EFFECT_JUMP_CHARGE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 255 SPELL_EFFECT_LEARN_TRANSMOG_SET
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 256 SPELL_EFFECT_256
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 257 SPELL_EFFECT_257
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 258 SPELL_EFFECT_MODIFY_KEYSTONE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 259 SPELL_EFFECT_RESPEC_AZERITE_EMPOWERED_ITEM
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 260 SPELL_EFFECT_SUMMON_STABLED_PET
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 261 SPELL_EFFECT_SCRAP_ITEM
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 262 SPELL_EFFECT_262
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 263 SPELL_EFFECT_REPAIR_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 264 SPELL_EFFECT_REMOVE_GEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 265 SPELL_EFFECT_LEARN_AZERITE_ESSENCE_POWER
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 266 SPELL_EFFECT_SET_ITEM_BONUS_LIST_GROUP_ENTRY
    {EFFECT_IMPLICIT_TARGET_CASTER,   TARGET_OBJECT_TYPE_UNIT}, // 267 SPELL_EFFECT_CREATE_PRIVATE_CONVERSATION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 268 SPELL_EFFECT_APPLY_MOUNT_EQUIPMENT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 269 SPELL_EFFECT_INCREASE_ITEM_BONUS_LIST_GROUP_STEP
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 270 SPELL_EFFECT_270
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 271 SPELL_EFFECT_APPLY_AREA_AURA_PARTY_NONRANDOM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 272 SPELL_EFFECT_SET_COVENANT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 273 SPELL_EFFECT_CRAFT_RUNEFORGE_LEGENDARY
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 274 SPELL_EFFECT_274
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 275 SPELL_EFFECT_275
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 276 SPELL_EFFECT_LEARN_TRANSMOG_ILLUSION
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 277 SPELL_EFFECT_SET_CHROMIE_TIME
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 278 SPELL_EFFECT_278
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 279 SPELL_EFFECT_LEARN_GARR_TALENT
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 280 SPELL_EFFECT_280
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 281 SPELL_EFFECT_LEARN_SOULBIND_CONDUIT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 282 SPELL_EFFECT_CONVERT_ITEMS_TO_CURRENCY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 283 SPELL_EFFECT_COMPLETE_CAMPAIGN
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 284 SPELL_EFFECT_SEND_CHAT_MESSAGE
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 285 SPELL_EFFECT_MODIFY_KEYSTONE_2
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 286 SPELL_EFFECT_GRANT_BATTLEPET_EXPERIENCE
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 287 SPELL_EFFECT_SET_GARRISON_FOLLOWER_LEVEL
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 288 SPELL_EFFECT_CRAFT_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 289 SPELL_EFFECT_MODIFY_AURA_STACKS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 290 SPELL_EFFECT_MODIFY_COOLDOWN
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 291 SPELL_EFFECT_MODIFY_COOLDOWNS
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 292 SPELL_EFFECT_MODIFY_COOLDOWNS_BY_CATEGORY
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 293 SPELL_EFFECT_MODIFY_CHARGES
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 294 SPELL_EFFECT_CRAFT_LOOT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 295 SPELL_EFFECT_SALVAGE_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 296 SPELL_EFFECT_CRAFT_SALVAGE_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 297 SPELL_EFFECT_RECRAFT_ITEM
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 298 SPELL_EFFECT_CANCEL_ALL_PRIVATE_CONVERSATIONS
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 299 SPELL_EFFECT_299
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 300 SPELL_EFFECT_300
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 301 SPELL_EFFECT_CRAFT_ENCHANT
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_NONE}, // 302 SPELL_EFFECT_GATHERING
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 303 SPELL_EFFECT_CREATE_TRAIT_TREE_CONFIG
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 304 SPELL_EFFECT_CHANGE_ACTIVE_COMBAT_TRAIT_CONFIG
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 305 SPELL_EFFECT_305
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 306 SPELL_EFFECT_UPDATE_INTERACTIONS
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 307 SPELL_EFFECT_307
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 308 SPELL_EFFECT_CANCEL_PRELOAD_WORLD
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 309 SPELL_EFFECT_PRELOAD_WORLD
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 310 SPELL_EFFECT_310
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 311 SPELL_EFFECT_ENSURE_WORLD_LOADED
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 312 SPELL_EFFECT_312
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_ITEM}, // 313 SPELL_EFFECT_CHANGE_ITEM_BONUSES_2
    {EFFECT_IMPLICIT_TARGET_NONE,     TARGET_OBJECT_TYPE_NONE}, // 314 SPELL_EFFECT_ADD_SOCKET_BONUS
    {EFFECT_IMPLICIT_TARGET_CASTER,   TARGET_OBJECT_TYPE_UNIT}, // 315 SPELL_EFFECT_LEARN_TRANSMOG_APPEARANCE_FROM_ITEM_MOD_APPEARANCE_GROUP
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 316 SPELL_EFFECT_KILL_CREDIT_LABEL_1
    {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 317 SPELL_EFFECT_KILL_CREDIT_LABEL_2
} };

SpellInfo::SpellInfo(SpellNameEntry const* spellName, ::Difficulty difficulty, SpellInfoLoadHelper const& data)
    : Id(spellName->ID), Difficulty(difficulty)
{
    _effects.reserve(32);
    for (SpellEffectEntry const* spellEffect : data.Effects)
    {
        if (!spellEffect)
            continue;

        Trinity::Containers::EnsureWritableVectorIndex(_effects, spellEffect->EffectIndex) = SpellEffectInfo(this, *spellEffect);
    }

    // Correct EffectIndex for blank effects
    for (size_t i = 0; i < _effects.size(); ++i)
    {
        _effects[i]._spellInfo = this;
        _effects[i].EffectIndex = SpellEffIndex(i);
    }

    _effects.shrink_to_fit();

    SpellName = &spellName->Name;

    // SpellMiscEntry
    if (SpellMiscEntry const* _misc = data.Misc)
    {
        Attributes = _misc->Attributes[0];
        AttributesEx = _misc->Attributes[1];
        AttributesEx2 = _misc->Attributes[2];
        AttributesEx3 = _misc->Attributes[3];
        AttributesEx4 = _misc->Attributes[4];
        AttributesEx5 = _misc->Attributes[5];
        AttributesEx6 = _misc->Attributes[6];
        AttributesEx7 = _misc->Attributes[7];
        AttributesEx8 = _misc->Attributes[8];
        AttributesEx9 = _misc->Attributes[9];
        AttributesEx10 = _misc->Attributes[10];
        AttributesEx11 = _misc->Attributes[11];
        AttributesEx12 = _misc->Attributes[12];
        AttributesEx13 = _misc->Attributes[13];
        AttributesEx14 = _misc->Attributes[14];
        CastTimeEntry = sSpellCastTimesStore.LookupEntry(_misc->CastingTimeIndex);
        DurationEntry = sSpellDurationStore.LookupEntry(_misc->DurationIndex);
        RangeEntry = sSpellRangeStore.LookupEntry(_misc->RangeIndex);
        Speed = _misc->Speed;
        LaunchDelay = _misc->LaunchDelay;
        SchoolMask = _misc->SchoolMask;
        IconFileDataId = _misc->SpellIconFileDataID;
        ActiveIconFileDataId = _misc->ActiveIconFileDataID;
        ContentTuningId = _misc->ContentTuningID;
        ShowFutureSpellPlayerConditionID = _misc->ShowFutureSpellPlayerConditionID;
    }

    // SpellScalingEntry
    if (SpellScalingEntry const* _scaling = data.Scaling)
    {
        Scaling.MinScalingLevel = _scaling->MinScalingLevel;
        Scaling.MaxScalingLevel = _scaling->MaxScalingLevel;
        Scaling.ScalesFromItemLevel = _scaling->ScalesFromItemLevel;
    }

    // SpellAuraOptionsEntry
    if (SpellAuraOptionsEntry const* _options = data.AuraOptions)
    {
        ProcFlags = _options->ProcTypeMask;
        ProcChance = _options->ProcChance;
        ProcCharges = _options->ProcCharges;
        ProcCooldown = _options->ProcCategoryRecovery;
        StackAmount = _options->CumulativeAura;
        if (SpellProcsPerMinuteEntry const* _ppm = sSpellProcsPerMinuteStore.LookupEntry(_options->SpellProcsPerMinuteID))
        {
            ProcBasePPM = _ppm->BaseProcRate;
            ProcPPMMods = sDB2Manager.GetSpellProcsPerMinuteMods(_ppm->ID);
        }
    }

    // SpellAuraRestrictionsEntry
    if (SpellAuraRestrictionsEntry const* _aura = data.AuraRestrictions)
    {
        CasterAuraState = _aura->CasterAuraState;
        TargetAuraState = _aura->TargetAuraState;
        ExcludeCasterAuraState = _aura->ExcludeCasterAuraState;
        ExcludeTargetAuraState = _aura->ExcludeTargetAuraState;
        CasterAuraSpell = _aura->CasterAuraSpell;
        TargetAuraSpell = _aura->TargetAuraSpell;
        ExcludeCasterAuraSpell = _aura->ExcludeCasterAuraSpell;
        ExcludeTargetAuraSpell = _aura->ExcludeTargetAuraSpell;
        CasterAuraType = AuraType(_aura->CasterAuraType);
        TargetAuraType = AuraType(_aura->TargetAuraType);
        ExcludeCasterAuraType = AuraType(_aura->ExcludeCasterAuraType);
        ExcludeTargetAuraType = AuraType(_aura->ExcludeTargetAuraType);
    }

    // SpellCastingRequirementsEntry
    if (SpellCastingRequirementsEntry const* _castreq = data.CastingRequirements)
    {
        RequiresSpellFocus = _castreq->RequiresSpellFocus;
        FacingCasterFlags = _castreq->FacingCasterFlags;
        RequiredAreasID = _castreq->RequiredAreasID;
    }

    // SpellCategoriesEntry
    if (SpellCategoriesEntry const* _categories = data.Categories)
    {
        CategoryId = _categories->Category;
        Dispel = _categories->DispelType;
        Mechanic = _categories->Mechanic;
        StartRecoveryCategory = _categories->StartRecoveryCategory;
        DmgClass = _categories->DefenseType;
        PreventionType = _categories->PreventionType;
        ChargeCategoryId = _categories->ChargeCategory;
    }

    // SpellClassOptionsEntry
    if (SpellClassOptionsEntry const* _class = data.ClassOptions)
    {
        SpellFamilyName = _class->SpellClassSet;
        SpellFamilyFlags = _class->SpellClassMask;
    }

    // SpellCooldownsEntry
    if (SpellCooldownsEntry const* _cooldowns = data.Cooldowns)
    {
        RecoveryTime = _cooldowns->RecoveryTime;
        CategoryRecoveryTime = _cooldowns->CategoryRecoveryTime;
        StartRecoveryTime = _cooldowns->StartRecoveryTime;
        CooldownAuraSpellId = _cooldowns->AuraSpellID;
    }

    // SpellEmpowerStageEntry
    std::ranges::transform(data.EmpowerStages, std::back_inserter(EmpowerStageThresholds), [](SpellEmpowerStageEntry const* stage) { return Milliseconds(stage->DurationMs); });

    // SpellEquippedItemsEntry
    if (SpellEquippedItemsEntry const* _equipped = data.EquippedItems)
    {
        EquippedItemClass = _equipped->EquippedItemClass;
        EquippedItemSubClassMask = _equipped->EquippedItemSubclass;
        EquippedItemInventoryTypeMask = _equipped->EquippedItemInvTypes;
    }

    // SpellInterruptsEntry
    if (SpellInterruptsEntry const* _interrupt = data.Interrupts)
    {
        InterruptFlags = SpellInterruptFlags(_interrupt->InterruptFlags);
        AuraInterruptFlags = SpellAuraInterruptFlags(_interrupt->AuraInterruptFlags[0]);
        AuraInterruptFlags2 = SpellAuraInterruptFlags2(_interrupt->AuraInterruptFlags[1]);
        ChannelInterruptFlags = SpellAuraInterruptFlags(_interrupt->ChannelInterruptFlags[0]);
        ChannelInterruptFlags2 = SpellAuraInterruptFlags2(_interrupt->ChannelInterruptFlags[1]);
    }

    for (SpellLabelEntry const* label : data.Labels)
        Labels.insert(label->LabelID);

    // SpellLevelsEntry
    if (SpellLevelsEntry const* _levels = data.Levels)
    {
        MaxLevel = _levels->MaxLevel;
        BaseLevel = _levels->BaseLevel;
        SpellLevel = _levels->SpellLevel;
    }

    // SpellPowerEntry
    PowerCosts = data.Powers;

    // SpellReagentsEntry
    if (SpellReagentsEntry const* _reagents = data.Reagents)
    {
        Reagent = _reagents->Reagent;
        ReagentCount = _reagents->ReagentCount;
    }

    ReagentsCurrency = data.ReagentsCurrency;

    // SpellShapeshiftEntry
    if (SpellShapeshiftEntry const* _shapeshift = data.Shapeshift)
    {
        Stances = MAKE_PAIR64(_shapeshift->ShapeshiftMask[0], _shapeshift->ShapeshiftMask[1]);
        StancesNot = MAKE_PAIR64(_shapeshift->ShapeshiftExclude[0], _shapeshift->ShapeshiftExclude[1]);
    }

    // SpellTargetRestrictionsEntry
    if (SpellTargetRestrictionsEntry const* _target = data.TargetRestrictions)
    {
        ConeAngle = _target->ConeDegrees;
        Width = _target->Width;
        Targets = _target->Targets;
        TargetCreatureType = _target->TargetCreatureType;
        MaxAffectedTargets = _target->MaxTargets;
        MaxTargetLevel = _target->MaxTargetLevel;
    }

    // SpellTotemsEntry
    if (SpellTotemsEntry const* _totem = data.Totems)
    {
        TotemCategory = _totem->RequiredTotemCategoryID;
        Totem = _totem->Totem;
    }

    _visuals = data.Visuals;
}

SpellInfo::SpellInfo(SpellNameEntry const* spellName, ::Difficulty difficulty, std::vector<SpellEffectEntry> const& effects)
    : Id(spellName->ID), Difficulty(difficulty)
{
    SpellName = &spellName->Name;

    _effects.reserve(32);
    for (SpellEffectEntry const& spellEffect : effects)
         Trinity::Containers::EnsureWritableVectorIndex(_effects, spellEffect.EffectIndex) = SpellEffectInfo(this, spellEffect);

    // Correct EffectIndex for blank effects
    for (size_t i = 0; i < _effects.size(); ++i)
    {
        _effects[i]._spellInfo = this;
        _effects[i].EffectIndex = SpellEffIndex(i);
    }

    _effects.shrink_to_fit();
}

SpellInfo::~SpellInfo()
{
    _UnloadImplicitTargetConditionLists();
}

uint32 SpellInfo::GetCategory() const
{
    return CategoryId;
}

bool SpellInfo::HasEffect(SpellEffectName effect) const
{
    for (SpellEffectInfo const& eff : GetEffects())
        if (eff.IsEffect(effect))
            return true;

    return false;
}

bool SpellInfo::HasAura(AuraType aura) const
{
    for (SpellEffectInfo const& effect : GetEffects())
        if (effect.IsAura(aura))
            return true;

    return false;
}

bool SpellInfo::HasAreaAuraEffect() const
{
    for (SpellEffectInfo const& effect : GetEffects())
        if (effect.IsAreaAuraEffect())
            return true;

    return false;
}

bool SpellInfo::HasOnlyDamageEffects() const
{
    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (effect.IsEffect())
        {
            switch (effect.Effect)
            {
                case SPELL_EFFECT_WEAPON_DAMAGE:
                case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL:
                case SPELL_EFFECT_NORMALIZED_WEAPON_DMG:
                case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE:
                case SPELL_EFFECT_SCHOOL_DAMAGE:
                case SPELL_EFFECT_ENVIRONMENTAL_DAMAGE:
                case SPELL_EFFECT_HEALTH_LEECH:
                case SPELL_EFFECT_DAMAGE_FROM_MAX_HEALTH_PCT:
                    continue;
                default:
                    return false;
            }
        }
    }

    return true;
}

bool SpellInfo::HasTargetType(::Targets target) const
{
    for (SpellEffectInfo const& effect : GetEffects())
        if (effect.TargetA.GetTarget() == target || effect.TargetB.GetTarget() == target)
            return true;

    return false;
}

bool SpellInfo::CanBeInterrupted(WorldObject const* interruptCaster, Unit const* interruptTarget, bool ignoreImmunity /*= false*/) const
{
    return HasAttribute(SPELL_ATTR7_NO_UI_NOT_INTERRUPTIBLE)
        || HasChannelInterruptFlag(SpellAuraInterruptFlags::Damage | SpellAuraInterruptFlags::EnteringCombat)
        || (interruptTarget->IsPlayer() && InterruptFlags.HasFlag(SpellInterruptFlags::DamageCancelsPlayerOnly))
        || InterruptFlags.HasFlag(SpellInterruptFlags::DamageCancels)
        || (interruptCaster && interruptCaster->IsUnit() && interruptCaster->ToUnit()->HasAuraTypeWithMiscvalue(SPELL_AURA_ALLOW_INTERRUPT_SPELL, Id))
        || ((!(interruptTarget->GetMechanicImmunityMask() & (1 << MECHANIC_INTERRUPT)) || ignoreImmunity)
            && !interruptTarget->HasAuraTypeWithAffectMask(SPELL_AURA_PREVENT_INTERRUPT, this)
            && PreventionType & SPELL_PREVENTION_TYPE_SILENCE);
}

bool SpellInfo::HasAnyAuraInterruptFlag() const
{
    return AuraInterruptFlags != SpellAuraInterruptFlags::None || AuraInterruptFlags2 != SpellAuraInterruptFlags2::None;
}

bool SpellInfo::IsExplicitDiscovery() const
{
    if (GetEffects().size() < 2)
        return false;

    return ((GetEffect(EFFECT_0).Effect == SPELL_EFFECT_CREATE_RANDOM_ITEM
        || GetEffect(EFFECT_0).Effect == SPELL_EFFECT_CREATE_LOOT)
        && GetEffect(EFFECT_1).Effect == SPELL_EFFECT_SCRIPT_EFFECT)
        || Id == 64323;
}

bool SpellInfo::IsLootCrafting() const
{
    return HasEffect(SPELL_EFFECT_CREATE_RANDOM_ITEM) || HasEffect(SPELL_EFFECT_CREATE_LOOT);
}

bool SpellInfo::IsProfession() const
{
    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (effect.Effect == SPELL_EFFECT_SKILL)
        {
            uint32 skill = effect.MiscValue;

            if (IsProfessionSkill(skill))
                return true;
        }
    }
    return false;
}

bool SpellInfo::IsPrimaryProfession() const
{
    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (effect.Effect == SPELL_EFFECT_SKILL)
        {
            uint32 skill = effect.MiscValue;

            if (IsPrimaryProfessionSkill(skill))
                return true;
        }
    }
    return false;
}

bool SpellInfo::IsPrimaryProfessionFirstRank() const
{
    return IsPrimaryProfession() && GetRank() == 1;
}

bool SpellInfo::IsAbilityOfSkillType(uint32 skillType) const
{
    SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(Id);

    for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
        if (_spell_idx->second->SkillLine == skillType)
            return true;

    return false;
}

bool SpellInfo::IsAffectingArea() const
{
    for (SpellEffectInfo const& effect : GetEffects())
        if (effect.IsEffect() && (effect.IsTargetingArea() || effect.IsEffect(SPELL_EFFECT_PERSISTENT_AREA_AURA) || effect.IsAreaAuraEffect()))
            return true;

    return false;
}

// checks if spell targets are selected from area, doesn't include spell effects in check (like area wide auras for example)
bool SpellInfo::IsTargetingArea() const
{
    for (SpellEffectInfo const& effect : GetEffects())
        if (effect.IsEffect() && effect.IsTargetingArea())
            return true;

    return false;
}

bool SpellInfo::NeedsExplicitUnitTarget() const
{
    return (GetExplicitTargetMask() & TARGET_FLAG_UNIT_MASK) != 0;
}

bool SpellInfo::NeedsToBeTriggeredByCaster(SpellInfo const* triggeringSpell) const
{
    if (NeedsExplicitUnitTarget())
        return true;

    /*
    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (effect.IsEffect())
        {
            if (effect.TargetA.GetSelectionCategory() == TARGET_SELECT_CATEGORY_CHANNEL
                || effect.TargetB.GetSelectionCategory() == TARGET_SELECT_CATEGORY_CHANNEL)
                return true;
        }
    }
    */

    if (triggeringSpell->IsChanneled())
    {
        uint32 mask = 0;
        for (SpellEffectInfo const& effect : GetEffects())
        {
            if (effect.TargetA.GetTarget() != TARGET_UNIT_CASTER && effect.TargetA.GetTarget() != TARGET_DEST_CASTER
                && effect.TargetB.GetTarget() != TARGET_UNIT_CASTER && effect.TargetB.GetTarget() != TARGET_DEST_CASTER)
            {
                mask |= effect.GetProvidedTargetMask();
            }
        }

        if (mask & TARGET_FLAG_UNIT_MASK)
            return true;
    }

    return false;
}

bool SpellInfo::IsPassive() const
{
    return HasAttribute(SPELL_ATTR0_PASSIVE);
}

bool SpellInfo::IsAutocastable() const
{
    if (IsPassive())
        return false;
    if (HasAttribute(SPELL_ATTR1_NO_AUTOCAST_AI))
        return false;
    return true;
}

bool SpellInfo::IsAutocastEnabledByDefault() const
{
    return !HasAttribute(SPELL_ATTR9_AUTOCAST_OFF_BY_DEFAULT);
}

bool SpellInfo::IsStackableWithRanks() const
{
    if (IsPassive())
        return false;

    // All stance spells. if any better way, change it.
    for (SpellEffectInfo const& effect : GetEffects())
    {
        switch (SpellFamilyName)
        {
            case SPELLFAMILY_PALADIN:
                // Paladin aura Spell
                if (effect.Effect == SPELL_EFFECT_APPLY_AREA_AURA_RAID)
                    return false;
                break;
            case SPELLFAMILY_DRUID:
                // Druid form Spell
                if (effect.Effect == SPELL_EFFECT_APPLY_AURA &&
                    effect.ApplyAuraName == SPELL_AURA_MOD_SHAPESHIFT)
                    return false;
                break;
        }
    }

    return true;
}

bool SpellInfo::IsPassiveStackableWithRanks() const
{
    return IsPassive() && !HasEffect(SPELL_EFFECT_APPLY_AURA);
}

bool SpellInfo::IsMultiSlotAura() const
{
    return IsPassive() || Id == 55849 || Id == 40075 || Id == 44413; // Power Spark, Fel Flak Fire, Incanter's Absorption
}

bool SpellInfo::IsStackableOnOneSlotWithDifferentCasters() const
{
    /// TODO: Re-verify meaning of SPELL_ATTR3_STACK_FOR_DIFF_CASTERS and update conditions here
    return StackAmount > 1 && !IsChanneled() && !HasAttribute(SPELL_ATTR3_DOT_STACKING_RULE);
}

bool SpellInfo::IsCooldownStartedOnEvent() const
{
    if (HasAttribute(SPELL_ATTR0_COOLDOWN_ON_EVENT))
        return true;

    SpellCategoryEntry const* category = sSpellCategoryStore.LookupEntry(CategoryId);
    return category && category->Flags & SPELL_CATEGORY_FLAG_COOLDOWN_STARTS_ON_EVENT;
}

bool SpellInfo::IsDeathPersistent() const
{
    return HasAttribute(SPELL_ATTR3_ALLOW_AURA_WHILE_DEAD);
}

bool SpellInfo::IsRequiringDeadTarget() const
{
    return HasAttribute(SPELL_ATTR3_ONLY_ON_GHOSTS);
}

bool SpellInfo::IsAllowingDeadTarget() const
{
    if (HasAttribute(SPELL_ATTR2_ALLOW_DEAD_TARGET) || Targets & (TARGET_FLAG_CORPSE_ALLY | TARGET_FLAG_CORPSE_ENEMY | TARGET_FLAG_UNIT_DEAD))
        return true;

    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (!effect.IsEffect())
            continue;

        if (effect.TargetA.GetObjectType() == TARGET_OBJECT_TYPE_CORPSE || effect.TargetB.GetObjectType() == TARGET_OBJECT_TYPE_CORPSE)
            return true;
    }

    return false;
}

bool SpellInfo::IsGroupBuff() const
{
    for (SpellEffectInfo const& effect : GetEffects())
    {
        switch (effect.TargetA.GetCheckType())
        {
            case TARGET_CHECK_PARTY:
            case TARGET_CHECK_RAID:
            case TARGET_CHECK_RAID_CLASS:
                return true;
            default:
                break;
        }
    }

    return false;
}

bool SpellInfo::CanBeUsedInCombat(Unit const* caster) const
{
    return !HasAttribute(SPELL_ATTR0_NOT_IN_COMBAT_ONLY_PEACEFUL)
        || (caster->HasAuraType(SPELL_AURA_ALLOW_MOUNT_IN_COMBAT) && HasAura(SPELL_AURA_MOUNTED));
}

bool SpellInfo::IsPositive() const
{
    return NegativeEffects.none();
}

bool SpellInfo::IsPositiveEffect(uint8 effIndex) const
{
    return !NegativeEffects.test(effIndex);
}

bool SpellInfo::IsChanneled() const
{
    return HasAttribute(SpellAttr1(SPELL_ATTR1_IS_CHANNELLED | SPELL_ATTR1_IS_SELF_CHANNELLED));
}

bool SpellInfo::IsMoveAllowedChannel() const
{
    return IsChanneled() && !ChannelInterruptFlags.HasFlag(SpellAuraInterruptFlags::Moving | SpellAuraInterruptFlags::Turning);
}

bool SpellInfo::IsNextMeleeSwingSpell() const
{
    return HasAttribute(SpellAttr0(SPELL_ATTR0_ON_NEXT_SWING_NO_DAMAGE | SPELL_ATTR0_ON_NEXT_SWING));
}

bool SpellInfo::IsRangedWeaponSpell() const
{
    return (SpellFamilyName == SPELLFAMILY_HUNTER && !(SpellFamilyFlags[1] & 0x10000000)) // for 53352, cannot find better way
        || (EquippedItemSubClassMask & ITEM_SUBCLASS_MASK_WEAPON_RANGED)
        || (Attributes & SPELL_ATTR0_USES_RANGED_SLOT);
}

bool SpellInfo::IsAutoRepeatRangedSpell() const
{
    return HasAttribute(SPELL_ATTR2_AUTO_REPEAT);
}

bool SpellInfo::IsEmpowerSpell() const
{
    return !EmpowerStageThresholds.empty();
}

bool SpellInfo::HasInitialAggro() const
{
    return !(HasAttribute(SPELL_ATTR1_NO_THREAT) || HasAttribute(SPELL_ATTR2_NO_INITIAL_THREAT) || HasAttribute(SPELL_ATTR4_NO_HARMFUL_THREAT));
}

bool SpellInfo::HasHitDelay() const
{
    return Speed > 0.0f || LaunchDelay > 0.0f;
}

WeaponAttackType SpellInfo::GetAttackType() const
{
    WeaponAttackType result;
    switch (DmgClass)
    {
        case SPELL_DAMAGE_CLASS_MELEE:
            if (HasAttribute(SPELL_ATTR3_REQUIRES_OFF_HAND_WEAPON))
                result = OFF_ATTACK;
            else
                result = BASE_ATTACK;
            break;
        case SPELL_DAMAGE_CLASS_RANGED:
            result = IsRangedWeaponSpell() ? RANGED_ATTACK : BASE_ATTACK;
            break;
        default:
            // Wands
            if (IsAutoRepeatRangedSpell())
                result = RANGED_ATTACK;
            else
                result = BASE_ATTACK;
            break;
    }

    return result;
}

bool SpellInfo::IsItemFitToSpellRequirements(Item const* item) const
{
    // item neutral spell
    if (EquippedItemClass == -1)
        return true;

    // item dependent spell
    if (item && item->IsFitToSpellRequirements(this))
        return true;

    return false;
}

bool SpellInfo::IsAffected(uint32 familyName, flag128 const& familyFlags) const
{
    if (!familyName)
        return true;

    if (familyName != SpellFamilyName)
        return false;

    if (familyFlags && !(familyFlags & SpellFamilyFlags))
        return false;

    return true;
}

bool SpellInfo::IsAffectedBySpellMods() const
{
    return !HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS);
}

bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const
{
    if (!IsAffectedBySpellMods())
        return false;

    SpellInfo const* affectSpell = sSpellMgr->GetSpellInfo(mod->spellId, Difficulty);
    if (!affectSpell)
        return false;

    switch (mod->type)
    {
        case SPELLMOD_FLAT:
        case SPELLMOD_PCT:
            // TEMP: dont use IsAffected - !familyName and !familyFlags are not valid options for spell mods
            // TODO: investigate if the !familyName and !familyFlags conditions are even valid for all other (nonmod) uses of SpellInfo::IsAffected
            return affectSpell->SpellFamilyName == SpellFamilyName && static_cast<SpellModifierByClassMask const*>(mod)->mask & SpellFamilyFlags;
        case SPELLMOD_LABEL_FLAT:
            return HasLabel(static_cast<SpellFlatModifierByLabel const*>(mod)->value.LabelID);
        case SPELLMOD_LABEL_PCT:
            return HasLabel(static_cast<SpellPctModifierByLabel const*>(mod)->value.LabelID);
        default:
            break;
    }

    return false;
}

bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const
{
    // Dispels other auras on immunity, check if this spell makes the unit immune to aura
    if (HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT) && CanSpellProvideImmunityAgainstAura(auraSpellInfo))
        return true;

    return false;
}

bool SpellInfo::CanDispelAura(SpellInfo const* auraSpellInfo) const
{
    // These auras (like Divine Shield) can't be dispelled
    if (auraSpellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES))
        return false;

    return true;
}

bool SpellInfo::IsSingleTarget() const
{
    // all other single target spells have if it has AttributesEx5
    if (HasAttribute(SPELL_ATTR5_LIMIT_N))
        return true;

    return false;
}

bool SpellInfo::IsAuraExclusiveBySpecificWith(SpellInfo const* spellInfo) const
{
    SpellSpecificType spellSpec1 = GetSpellSpecific();
    SpellSpecificType spellSpec2 = spellInfo->GetSpellSpecific();
    switch (spellSpec1)
    {
        case SPELL_SPECIFIC_WARLOCK_ARMOR:
        case SPELL_SPECIFIC_MAGE_ARMOR:
        case SPELL_SPECIFIC_ELEMENTAL_SHIELD:
        case SPELL_SPECIFIC_MAGE_POLYMORPH:
        case SPELL_SPECIFIC_PRESENCE:
        case SPELL_SPECIFIC_CHARM:
        case SPELL_SPECIFIC_SCROLL:
        case SPELL_SPECIFIC_WARRIOR_ENRAGE:
        case SPELL_SPECIFIC_MAGE_ARCANE_BRILLANCE:
        case SPELL_SPECIFIC_PRIEST_DIVINE_SPIRIT:
            return spellSpec1 == spellSpec2;
        case SPELL_SPECIFIC_FOOD:
            return spellSpec2 == SPELL_SPECIFIC_FOOD
                || spellSpec2 == SPELL_SPECIFIC_FOOD_AND_DRINK;
        case SPELL_SPECIFIC_DRINK:
            return spellSpec2 == SPELL_SPECIFIC_DRINK
                || spellSpec2 == SPELL_SPECIFIC_FOOD_AND_DRINK;
        case SPELL_SPECIFIC_FOOD_AND_DRINK:
            return spellSpec2 == SPELL_SPECIFIC_FOOD
                || spellSpec2 == SPELL_SPECIFIC_DRINK
                || spellSpec2 == SPELL_SPECIFIC_FOOD_AND_DRINK;
        default:
            return false;
    }
}

bool SpellInfo::IsAuraExclusiveBySpecificPerCasterWith(SpellInfo const* spellInfo) const
{
    SpellSpecificType spellSpec = GetSpellSpecific();
    switch (spellSpec)
    {
        case SPELL_SPECIFIC_SEAL:
        case SPELL_SPECIFIC_HAND:
        case SPELL_SPECIFIC_AURA:
        case SPELL_SPECIFIC_STING:
        case SPELL_SPECIFIC_CURSE:
        case SPELL_SPECIFIC_BANE:
        case SPELL_SPECIFIC_ASPECT:
        case SPELL_SPECIFIC_WARLOCK_CORRUPTION:
            return spellSpec == spellInfo->GetSpellSpecific();
        default:
            return false;
    }
}

SpellCastResult SpellInfo::CheckShapeshift(uint32 form) const
{
    // talents that learn spells can have stance requirements that need ignore
    // (this requirement only for client-side stance show in talent description)
    /* TODO: 6.x fix this in proper way (probably spell flags/attributes?)
    if (GetTalentSpellCost(Id) > 0 && HasEffect(SPELL_EFFECT_LEARN_SPELL))
        return SPELL_CAST_OK;*/

    //if (HasAttribute(SPELL_ATTR13_ACTIVATES_REQUIRED_SHAPESHIFT))
    //    return SPELL_CAST_OK;

    uint64 stanceMask = (form ? UI64LIT(1) << (form - 1) : 0);

    if (stanceMask & StancesNot)                 // can explicitly not be cast in this stance
        return SPELL_FAILED_NOT_SHAPESHIFT;

    if (stanceMask & Stances)                    // can explicitly be cast in this stance
        return SPELL_CAST_OK;

    bool actAsShifted = false;
    SpellShapeshiftFormEntry const* shapeInfo = nullptr;
    if (form > 0)
    {
        shapeInfo = sSpellShapeshiftFormStore.LookupEntry(form);
        if (!shapeInfo)
        {
            TC_LOG_ERROR("spells", "GetErrorAtShapeshiftedCast: unknown shapeshift {}", form);
            return SPELL_CAST_OK;
        }
        actAsShifted = !shapeInfo->GetFlags().HasFlag(SpellShapeshiftFormFlags::Stance);
    }

    if (actAsShifted)
    {
        if (HasAttribute(SPELL_ATTR0_NOT_SHAPESHIFTED) || (shapeInfo && shapeInfo->GetFlags().HasFlag(SpellShapeshiftFormFlags::CanOnlyCastShapeshiftSpells))) // not while shapeshifted
            return SPELL_FAILED_NOT_SHAPESHIFT;
        else if (Stances != 0)                   // needs other shapeshift
            return SPELL_FAILED_ONLY_SHAPESHIFT;
    }
    else
    {
        // needs shapeshift
        if (!HasAttribute(SPELL_ATTR2_ALLOW_WHILE_NOT_SHAPESHIFTED_CASTER_FORM) && Stances != 0)
            return SPELL_FAILED_ONLY_SHAPESHIFT;
    }

    return SPELL_CAST_OK;
}

SpellCastResult SpellInfo::CheckLocation(uint32 map_id, uint32 zone_id, uint32 area_id, Player const* player) const
{
    // normal case
    if (RequiredAreasID > 0)
    {
        bool found = false;
        std::vector<uint32> areaGroupMembers = sDB2Manager.GetAreasForGroup(RequiredAreasID);
        for (uint32 areaId : areaGroupMembers)
        {
            if (DB2Manager::IsInArea(area_id, areaId))
            {
                found = true;
                break;
            }
        }

        if (!found)
            return SPELL_FAILED_INCORRECT_AREA;
    }

    // continent limitation (virtual continent)
    if (HasAttribute(SPELL_ATTR4_ONLY_FLYING_AREAS))
    {
        EnumFlag<AreaMountFlags> mountFlags = AreaMountFlags::None;
        if (player && player->HasAuraType(SPELL_AURA_MOUNT_RESTRICTIONS))
        {
            for (AuraEffect const* auraEffect : player->GetAuraEffectsByType(SPELL_AURA_MOUNT_RESTRICTIONS))
                mountFlags |= AreaMountFlags(auraEffect->GetMiscValue());
        }
        else if (AreaTableEntry const* areaTable = sAreaTableStore.LookupEntry(area_id))
            mountFlags = areaTable->GetMountFlags();

        if (!(mountFlags.HasFlag(AreaMountFlags::AllowFlyingMounts)))
            return SPELL_FAILED_INCORRECT_AREA;

        if (player && !ConditionMgr::IsPlayerMeetingCondition(player, 72968)) // Hardcoded PlayerCondition id for attribute check in client
            return SPELL_FAILED_INCORRECT_AREA;
    }

    // raid instance limitation
    if (HasAttribute(SPELL_ATTR6_NOT_IN_RAID_INSTANCES))
    {
        MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
        if (!mapEntry || mapEntry->IsRaid())
            return SPELL_FAILED_NOT_IN_RAID_INSTANCE;
    }

    if (HasAttribute(SPELL_ATTR8_REMOVE_OUTSIDE_DUNGEONS_AND_RAIDS))
    {
        MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
        if (!mapEntry || !mapEntry->IsDungeon())
            return SPELL_FAILED_TARGET_NOT_IN_INSTANCE;
    }

    if (HasAttribute(SPELL_ATTR8_NOT_IN_BATTLEGROUND))
    {
        MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
        if (!mapEntry || mapEntry->IsBattleground())
            return SPELL_FAILED_NOT_IN_BATTLEGROUND;
    }

    // DB base check (if non empty then must fit at least single for allow)
    SpellAreaMapBounds saBounds = sSpellMgr->GetSpellAreaMapBounds(Id);
    if (saBounds.first != saBounds.second)
    {
        for (SpellAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
        {
            if (itr->second.IsFitToRequirements(player, zone_id, area_id))
                return SPELL_CAST_OK;
        }
        return SPELL_FAILED_INCORRECT_AREA;
    }

    // bg spell checks
    switch (Id)
    {
        case 23333:                                         // Warsong Flag
        case 23335:                                         // Silverwing Flag
            return map_id == 489 && player && player->InBattleground() ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
        case 2584:                                          // Waiting to Resurrect
        case 42792:                                         // Recently Dropped Flag
        case 43681:                                         // Inactive
        case 44535:                                         // Spirit Heal (mana)
        {
            MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
            if (!mapEntry)
                return SPELL_FAILED_INCORRECT_AREA;

            return zone_id == AREA_WINTERGRASP || (mapEntry->IsBattleground() && player && player->InBattleground()) ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
        }
        case 44521:                                         // Preparation
        {
            if (!player)
                return SPELL_FAILED_REQUIRES_AREA;

            MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
            if (!mapEntry)
                return SPELL_FAILED_INCORRECT_AREA;

            if (!mapEntry->IsBattleground())
                return SPELL_FAILED_REQUIRES_AREA;

            Battleground* bg = player->GetBattleground();
            return bg && bg->GetStatus() == STATUS_WAIT_JOIN ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
        }
        case 32724:                                         // Gold Team (Alliance)
        case 32725:                                         // Green Team (Alliance)
        case 35774:                                         // Gold Team (Horde)
        case 35775:                                         // Green Team (Horde)
        {
            MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
            if (!mapEntry)
                return SPELL_FAILED_INCORRECT_AREA;

            return mapEntry->IsBattleArena() && player && player->InBattleground() ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
        }
        case 32727:                                         // Arena Preparation
        {
            if (!player)
                return SPELL_FAILED_REQUIRES_AREA;

            MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
            if (!mapEntry)
                return SPELL_FAILED_INCORRECT_AREA;

            if (!mapEntry->IsBattleArena())
                return SPELL_FAILED_REQUIRES_AREA;

            Battleground* bg = player->GetBattleground();
            return bg && bg->GetStatus() == STATUS_WAIT_JOIN ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
        }
    }

    // aura limitations
    if (player)
    {
        for (SpellEffectInfo const& effect : GetEffects())
        {
            if (!effect.IsAura())
                continue;

            switch (effect.ApplyAuraName)
            {
                case SPELL_AURA_MOD_SHAPESHIFT:
                {
                    if (SpellShapeshiftFormEntry const* spellShapeshiftForm = sSpellShapeshiftFormStore.LookupEntry(effect.MiscValue))
                        if (uint32 mountType = spellShapeshiftForm->MountTypeID)
                            if (!player->GetMountCapability(mountType))
                                return SPELL_FAILED_NOT_HERE;
                    break;
                }
                case SPELL_AURA_MOUNTED:
                {
                    uint32 mountType = effect.MiscValueB;
                    if (MountEntry const* mountEntry = sDB2Manager.GetMount(Id))
                        mountType = mountEntry->MountTypeID;
                    if (mountType && !player->GetMountCapability(mountType))
                        return SPELL_FAILED_NOT_HERE;
                    break;
                }
                default:
                    break;
            }
        }
    }

    return SPELL_CAST_OK;
}

SpellCastResult SpellInfo::CheckTarget(WorldObject const* caster, WorldObject const* target, bool implicit /*= true*/) const
{
    if (HasAttribute(SPELL_ATTR1_EXCLUDE_CASTER) && caster == target)
        return SPELL_FAILED_BAD_TARGETS;

    // check visibility - ignore invisibility/stealth for implicit (area) targets
    if (!HasAttribute(SPELL_ATTR6_IGNORE_PHASE_SHIFT) && !caster->CanSeeOrDetect(target, implicit))
        return SPELL_FAILED_BAD_TARGETS;

    Unit const* unitTarget = target->ToUnit();

    if (HasAttribute(SPELL_ATTR8_ONLY_TARGET_IF_SAME_CREATOR))
    {
        auto getCreatorOrSelf = [](WorldObject const* obj)
        {
            ObjectGuid creator = obj->GetCreatorGUID();
            if (creator.IsEmpty())
                creator = obj->GetGUID();

            return creator;
        };
        if (getCreatorOrSelf(caster) != getCreatorOrSelf(target))
            return SPELL_FAILED_BAD_TARGETS;
    }

    // creature/player specific target checks
    if (unitTarget)
    {
        // spells cannot be cast if target has a pet in combat either
        if (HasAttribute(SPELL_ATTR1_ONLY_PEACEFUL_TARGETS) && (unitTarget->IsInCombat() || unitTarget->HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT)))
            return SPELL_FAILED_TARGET_AFFECTING_COMBAT;

        // only spells with SPELL_ATTR3_ONLY_TARGET_GHOSTS can target ghosts
        if (HasAttribute(SPELL_ATTR3_ONLY_ON_GHOSTS) != unitTarget->HasAuraType(SPELL_AURA_GHOST))
        {
            if (HasAttribute(SPELL_ATTR3_ONLY_ON_GHOSTS))
                return SPELL_FAILED_TARGET_NOT_GHOST;
            else
                return SPELL_FAILED_BAD_TARGETS;
        }

        if (caster != unitTarget)
        {
            if (caster->GetTypeId() == TYPEID_PLAYER)
            {
                // Do not allow these spells to target creatures not tapped by us (Banish, Polymorph, many quest spells)
                if (HasAttribute(SPELL_ATTR2_CANNOT_CAST_ON_TAPPED))
                    if (Creature const* targetCreature = unitTarget->ToCreature())
                        if (targetCreature->hasLootRecipient() && !targetCreature->isTappedBy(caster->ToPlayer()))
                            return SPELL_FAILED_CANT_CAST_ON_TAPPED;

                if (HasAttribute(SPELL_ATTR0_CU_PICKPOCKET))
                {
                    Creature const* targetCreature = unitTarget->ToCreature();
                    if (!targetCreature)
                        return SPELL_FAILED_BAD_TARGETS;

                    if (!targetCreature->CanHaveLoot() || !LootTemplates_Pickpocketing.HaveLootFor(targetCreature->GetCreatureDifficulty()->PickPocketLootID))
                        return SPELL_FAILED_TARGET_NO_POCKETS;
                }

                // Not allow disarm unarmed player
                if (Mechanic == MECHANIC_DISARM)
                {
                    if (unitTarget->GetTypeId() == TYPEID_PLAYER)
                    {
                        Player const* player = unitTarget->ToPlayer();
                        if (!player->GetWeaponForAttack(BASE_ATTACK) || !player->IsUseEquipedWeapon(true))
                            return SPELL_FAILED_TARGET_NO_WEAPONS;
                    }
                    else if (!unitTarget->GetVirtualItemId(0))
                        return SPELL_FAILED_TARGET_NO_WEAPONS;
                }
            }
        }

        if (HasAttribute(SPELL_ATTR8_ONLY_TARGET_OWN_SUMMONS))
            if (!unitTarget->IsSummon() || unitTarget->ToTempSummon()->GetSummonerGUID() != caster->GetGUID())
                return SPELL_FAILED_BAD_TARGETS;

        if (HasAttribute(SPELL_ATTR3_NOT_ON_AOE_IMMUNE))
            if (unitTarget->GetSpellOtherImmunityMask().HasFlag(SpellOtherImmunity::AoETarget))
                return SPELL_FAILED_BAD_TARGETS;

        if (HasAttribute(SPELL_ATTR9_TARGET_MUST_BE_GROUNDED) &&
            (unitTarget->HasUnitMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING | MOVEMENTFLAG_HOVER) ||
                unitTarget->HasExtraUnitMovementFlag2(MOVEMENTFLAG3_ADV_FLYING)))
            return SPELL_FAILED_TARGET_NOT_GROUNDED;
    }
    // corpse specific target checks
    else if (Corpse const* corpseTarget = target->ToCorpse())
    {
        // cannot target bare bones
        if (corpseTarget->GetType() == CORPSE_BONES)
            return SPELL_FAILED_BAD_TARGETS;
        // we have to use owner for some checks (aura preventing resurrection for example)
        if (Player* owner = ObjectAccessor::FindPlayer(corpseTarget->GetOwnerGUID()))
            unitTarget = owner;
        // we're not interested in corpses without owner
        else
            return SPELL_FAILED_BAD_TARGETS;
    }
    // other types of objects - always valid
    else return SPELL_CAST_OK;

    // corpseOwner and unit specific target checks
    if (!unitTarget->IsPlayer())
    {
        if (HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
            return SPELL_FAILED_TARGET_NOT_PLAYER;

        if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && unitTarget->IsControlledByPlayer())
            return SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED;
    }
    else if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
        return SPELL_FAILED_TARGET_IS_PLAYER;

    if (!IsAllowingDeadTarget() && !unitTarget->IsAlive())
       return SPELL_FAILED_TARGETS_DEAD;

    // check this flag only for implicit targets (chain and area), allow to explicitly target units for spells like Shield of Righteousness
    if (implicit && HasAttribute(SPELL_ATTR6_DO_NOT_CHAIN_TO_CROWD_CONTROLLED_TARGETS) && !unitTarget->CanFreeMove())
       return SPELL_FAILED_BAD_TARGETS;

    // checked in Unit::IsValidAttack/AssistTarget, shouldn't be checked for ENTRY targets
    //if (!HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE) && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNINTERACTIBLE))
    //    return SPELL_FAILED_BAD_TARGETS;

    if (!CheckTargetCreatureType(unitTarget))
    {
        if (target->GetTypeId() == TYPEID_PLAYER)
            return SPELL_FAILED_TARGET_IS_PLAYER;
        else
            return SPELL_FAILED_BAD_TARGETS;
    }

    // check GM mode and GM invisibility - only for player casts (npc casts are controlled by AI) and negative spells
    if (unitTarget != caster && (caster->GetAffectingPlayer() || !IsPositive()) && unitTarget->GetTypeId() == TYPEID_PLAYER)
    {
        if (!unitTarget->ToPlayer()->IsVisible())
            return SPELL_FAILED_BM_OR_INVISGOD;

        if (unitTarget->ToPlayer()->IsGameMaster())
            return SPELL_FAILED_BM_OR_INVISGOD;
    }

    // not allow casting on flying player
    if (unitTarget->HasUnitState(UNIT_STATE_IN_FLIGHT) && !HasAttribute(SPELL_ATTR0_CU_ALLOW_INFLIGHT_TARGET))
        return SPELL_FAILED_BAD_TARGETS;

    /* TARGET_UNIT_MASTER gets blocked here for passengers, because the whole idea of this check is to
    not allow passengers to be implicitly hit by spells, however this target type should be an exception,
    if this is left it kills spells that award kill credit from vehicle to master (few spells),
    the use of these 2 covers passenger target check, logically, if vehicle cast this to master it should always hit
    him, because it would be it's passenger, there's no such case where this gets to fail legitimacy, this problem
    cannot be solved from within the check in other way since target type cannot be called for the spell currently
    Spell examples: [ID - 52864 Devour Water, ID - 52862 Devour Wind, ID - 49370 Wyrmrest Defender: Destabilize Azure Dragonshrine Effect] */
    if (Unit const* unitCaster = caster->ToUnit())
    {
        if (!unitCaster->IsVehicle() && !(unitCaster->GetCharmerOrOwner() == target))
        {
            if (TargetAuraState && !unitTarget->HasAuraState(AuraStateType(TargetAuraState), this, unitCaster))
                return SPELL_FAILED_TARGET_AURASTATE;

            if (ExcludeTargetAuraState && unitTarget->HasAuraState(AuraStateType(ExcludeTargetAuraState), this, unitCaster))
                return SPELL_FAILED_TARGET_AURASTATE;
        }
    }

    if (TargetAuraSpell && !unitTarget->HasAura(TargetAuraSpell))
        return SPELL_FAILED_TARGET_AURASTATE;

    if (ExcludeTargetAuraSpell && unitTarget->HasAura(ExcludeTargetAuraSpell))
        return SPELL_FAILED_TARGET_AURASTATE;

    if (TargetAuraType && !unitTarget->HasAuraType(TargetAuraType))
        return SPELL_FAILED_TARGET_AURASTATE;

    if (ExcludeTargetAuraType && unitTarget->HasAuraType(ExcludeTargetAuraType))
        return SPELL_FAILED_TARGET_AURASTATE;

    if (unitTarget->HasAuraType(SPELL_AURA_PREVENT_RESURRECTION) && !HasAttribute(SPELL_ATTR7_BYPASS_NO_RESURRECT_AURA))
        if (HasEffect(SPELL_EFFECT_SELF_RESURRECT) || HasEffect(SPELL_EFFECT_RESURRECT))
            return SPELL_FAILED_TARGET_CANNOT_BE_RESURRECTED;

    if (HasAttribute(SPELL_ATTR8_ENFORCE_IN_COMBAT_RESSURECTION_LIMIT))
        if (Map* map = caster->GetMap())
            if (InstanceMap* iMap = map->ToInstanceMap())
                if (InstanceScript* instance = iMap->GetInstanceScript())
                    if (instance->GetCombatResurrectionCharges() == 0 && instance->IsEncounterInProgress())
                        return SPELL_FAILED_TARGET_CANNOT_BE_RESURRECTED;

    return SPELL_CAST_OK;
}

SpellCastResult SpellInfo::CheckExplicitTarget(WorldObject const* caster, WorldObject const* target, Item const* itemTarget /*= nullptr*/) const
{
    uint32 neededTargets = GetExplicitTargetMask();
    if (!target)
    {
        if (neededTargets & (TARGET_FLAG_UNIT_MASK | TARGET_FLAG_GAMEOBJECT_MASK | TARGET_FLAG_CORPSE_MASK))
            if (!(neededTargets & TARGET_FLAG_GAMEOBJECT_ITEM) || !itemTarget)
                return SPELL_FAILED_BAD_TARGETS;
        return SPELL_CAST_OK;
    }

    if (Unit const* unitTarget = target->ToUnit())
    {
        if (neededTargets & (TARGET_FLAG_UNIT_ENEMY | TARGET_FLAG_UNIT_ALLY | TARGET_FLAG_UNIT_RAID | TARGET_FLAG_UNIT_PARTY | TARGET_FLAG_UNIT_MINIPET | TARGET_FLAG_UNIT_PASSENGER))
        {
            Unit const* unitCaster = caster->ToUnit();
            if (neededTargets & TARGET_FLAG_UNIT_ENEMY)
                if (caster->IsValidAttackTarget(unitTarget, this))
                    return SPELL_CAST_OK;
            if ((neededTargets & TARGET_FLAG_UNIT_ALLY)
                || ((neededTargets & TARGET_FLAG_UNIT_PARTY) && unitCaster && unitCaster->IsInPartyWith(unitTarget))
                || ((neededTargets & TARGET_FLAG_UNIT_RAID) && unitCaster && unitCaster->IsInRaidWith(unitTarget)))
                    if (caster->IsValidAssistTarget(unitTarget, this))
                        return SPELL_CAST_OK;
            if ((neededTargets & TARGET_FLAG_UNIT_MINIPET) && unitCaster)
                if (unitTarget->GetGUID() == unitCaster->GetCritterGUID())
                    return SPELL_CAST_OK;
            if ((neededTargets & TARGET_FLAG_UNIT_PASSENGER) && unitCaster)
                if (unitTarget->IsOnVehicle(unitCaster))
                    return SPELL_CAST_OK;
            return SPELL_FAILED_BAD_TARGETS;
        }
    }
    return SPELL_CAST_OK;
}

SpellCastResult SpellInfo::CheckVehicle(Unit const* caster) const
{
    // All creatures should be able to cast as passengers freely, restriction and attribute are only for players
    if (caster->GetTypeId() != TYPEID_PLAYER)
        return SPELL_CAST_OK;

    Vehicle* vehicle = caster->GetVehicle();
    if (vehicle)
    {
        uint16 checkMask = 0;
        for (SpellEffectInfo const& effect : GetEffects())
        {
            if (effect.IsAura(SPELL_AURA_MOD_SHAPESHIFT))
            {
                SpellShapeshiftFormEntry const* shapeShiftFromEntry = sSpellShapeshiftFormStore.LookupEntry(effect.MiscValue);
                if (shapeShiftFromEntry && (shapeShiftFromEntry->Flags & 1) == 0)  // unk flag
                    checkMask |= VEHICLE_SEAT_FLAG_UNCONTROLLED;
                break;
            }
        }

        if (HasAura(SPELL_AURA_MOUNTED))
            checkMask |= VEHICLE_SEAT_FLAG_CAN_CAST_MOUNT_SPELL;

        if (!checkMask)
            checkMask = VEHICLE_SEAT_FLAG_CAN_ATTACK;

        VehicleSeatEntry const* vehicleSeat = vehicle->GetSeatForPassenger(caster);
        if (!HasAttribute(SPELL_ATTR6_ALLOW_WHILE_RIDING_VEHICLE) && !HasAttribute(SPELL_ATTR0_ALLOW_WHILE_MOUNTED)
            && (vehicleSeat->Flags & checkMask) != checkMask)
            return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW;

        // Can only summon uncontrolled minions/guardians when on controlled vehicle
        if (vehicleSeat->Flags & (VEHICLE_SEAT_FLAG_CAN_CONTROL | VEHICLE_SEAT_FLAG_UNK2))
        {
            for (SpellEffectInfo const& effect : GetEffects())
            {
                if (!effect.IsEffect(SPELL_EFFECT_SUMMON))
                    continue;

                SummonPropertiesEntry const* props = sSummonPropertiesStore.LookupEntry(effect.MiscValueB);
                if (props && props->Control != SUMMON_CATEGORY_WILD)
                    return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW;
            }
        }
    }

    return SPELL_CAST_OK;
}

bool SpellInfo::CheckTargetCreatureType(Unit const* target) const
{
    // Curse of Doom & Exorcism: not find another way to fix spell target check :/
    if (SpellFamilyName == SPELLFAMILY_WARLOCK && GetCategory() == 1179)
    {
        // not allow cast at player
        if (target->GetTypeId() == TYPEID_PLAYER)
            return false;
        else
            return true;
    }

    // if target is magnet (i.e Grounding Totem) the check is skipped
    if (target->IsMagnet())
        return true;

    uint32 creatureType = target->GetCreatureTypeMask();
    return !TargetCreatureType || !creatureType || (creatureType & TargetCreatureType) || target->HasAuraType(SPELL_AURA_IGNORE_SPELL_CREATURE_TYPE_REQUIREMENTS);
}

SpellSchoolMask SpellInfo::GetSchoolMask() const
{
    return SpellSchoolMask(SchoolMask);
}

uint64 SpellInfo::GetAllEffectsMechanicMask() const
{
    uint64 mask = 0;
    if (Mechanic)
        mask |= UI64LIT(1) << Mechanic;

    for (SpellEffectInfo const& effect : GetEffects())
        if (effect.IsEffect() && effect.Mechanic)
            mask |= UI64LIT(1) << effect.Mechanic;

    return mask;
}

uint64 SpellInfo::GetEffectMechanicMask(SpellEffIndex effIndex) const
{
    uint64 mask = 0;
    if (Mechanic)
        mask |= UI64LIT(1) << Mechanic;

    if (GetEffect(effIndex).IsEffect() && GetEffect(effIndex).Mechanic)
        mask |= UI64LIT(1) << GetEffect(effIndex).Mechanic;

    return mask;
}

uint64 SpellInfo::GetSpellMechanicMaskByEffectMask(uint32 effectMask) const
{
    uint64 mask = 0;
    if (Mechanic)
        mask |= UI64LIT(1) << Mechanic;

    for (SpellEffectInfo const& effect : GetEffects())
        if ((effectMask & (1 << effect.EffectIndex)) && effect.Mechanic)
            mask |= UI64LIT(1) << effect.Mechanic;

    return mask;
}

Mechanics SpellInfo::GetEffectMechanic(SpellEffIndex effIndex) const
{
    if (GetEffect(effIndex).IsEffect() && GetEffect(effIndex).Mechanic)
        return GetEffect(effIndex).Mechanic;

    if (Mechanic)
        return Mechanics(Mechanic);

    return MECHANIC_NONE;
}

uint32 SpellInfo::GetDispelMask() const
{
    return GetDispelMask(DispelType(Dispel));
}

uint32 SpellInfo::GetDispelMask(DispelType type)
{
    // If dispel all
    if (type == DISPEL_ALL)
        return DISPEL_ALL_MASK;
    else
        return uint32(1 << type);
}

uint32 SpellInfo::GetExplicitTargetMask() const
{
    return ExplicitTargetMask;
}

AuraStateType SpellInfo::GetAuraState() const
{
    return _auraState;
}

void SpellInfo::_LoadAuraState()
{
    _auraState = [this]()->AuraStateType
    {
        // Faerie Fire (Feral)
        if (GetCategory() == 1133)
            return AURA_STATE_FAERIE_FIRE;

        // Swiftmend state on Regrowth, Rejuvenation, Wild Growth
        if (SpellFamilyName == SPELLFAMILY_DRUID && (SpellFamilyFlags[0] & 0x50 || SpellFamilyFlags[1] & 0x4000000))
            return AURA_STATE_DRUID_PERIODIC_HEAL;

        // Deadly poison aura state
        if (SpellFamilyName == SPELLFAMILY_ROGUE && SpellFamilyFlags[0] & 0x10000)
            return AURA_STATE_ROGUE_POISONED;

        // Enrage aura state
        if (Dispel == DISPEL_ENRAGE)
            return AURA_STATE_ENRAGED;

        // Bleeding aura state
        if (GetAllEffectsMechanicMask() & (1 << MECHANIC_BLEED))
            return AURA_STATE_BLEED;

        if (GetSchoolMask() & SPELL_SCHOOL_MASK_FROST)
            for (SpellEffectInfo const& effect : GetEffects())
                if (effect.IsAura(SPELL_AURA_MOD_STUN) || effect.IsAura(SPELL_AURA_MOD_ROOT) || effect.IsAura(SPELL_AURA_MOD_ROOT_2))
                    return AURA_STATE_FROZEN;

        switch (Id)
        {
            case 1064: // Dazed
                return AURA_STATE_DAZED;
            case 32216: // Victorious
                return AURA_STATE_VICTORIOUS;
            case 71465: // Divine Surge
            case 50241: // Evasive Charges
            case 81262: // Efflorescence
                return AURA_STATE_RAID_ENCOUNTER;
            case 6950:   // Faerie Fire
            case 9806:   // Phantom Strike
            case 9991:   // Touch of Zanzil
            case 13424:  // Faerie Fire
            case 13752:  // Faerie Fire
            case 16432:  // Plague Mist
            case 20656:  // Faerie Fire
            case 25602:  // Faerie Fire
            case 32129:  // Faerie Fire
            case 35325:  // Glowing Blood
            case 35328:  // Lambent Blood
            case 35329:  // Vibrant Blood
            case 35331:  // Black Blood
            case 49163:  // Perpetual Instability
            case 65863:  // Faerie Fire
            case 79559:  // Luxscale Light
            case 82855:  // Dazzling
            case 102953: // In the Rumpus
            case 127907: // Phosphorescence
            case 127913: // Phosphorescence
            case 129007: // Zijin Sting
            case 130159: // Fae Touch
            case 142537: // Spotter Smoke
            case 168455: // Spotted!
            case 176905: // Super Sticky Glitter Bomb
            case 189502: // Marked
            case 201785: // Intruder Alert!
            case 201786: // Intruder Alert!
            case 201935: // Spotted!
            case 239233: // Smoke Bomb
            case 319400: // Glitter Burst
            case 321470: // Dimensional Shifter Mishap
            case 331134: // Spotted
                return AURA_STATE_FAERIE_FIRE;
            default:
                break;
        }

        if (Mechanic == MECHANIC_BANISH)
            return AURA_STATE_BANISHED;

        return AURA_STATE_NONE;
    }();
}

SpellSpecificType SpellInfo::GetSpellSpecific() const
{
    return _spellSpecific;
};

void SpellInfo::_LoadSpellSpecific()
{
    _spellSpecific = [this]()->SpellSpecificType
    {
        switch (SpellFamilyName)
        {
            case SPELLFAMILY_GENERIC:
            {
                // Food / Drinks (mostly)
                if (HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing))
                {
                    bool food = false;
                    bool drink = false;
                    for (SpellEffectInfo const& effect : GetEffects())
                    {
                        if (!effect.IsAura())
                            continue;
                        switch (effect.ApplyAuraName)
                        {
                            // Food
                            case SPELL_AURA_MOD_REGEN:
                            case SPELL_AURA_OBS_MOD_HEALTH:
                                food = true;
                                break;
                            // Drink
                            case SPELL_AURA_MOD_POWER_REGEN:
                            case SPELL_AURA_OBS_MOD_POWER:
                                drink = true;
                                break;
                            default:
                                break;
                        }
                    }

                    if (food && drink)
                        return SPELL_SPECIFIC_FOOD_AND_DRINK;
                    else if (food)
                        return SPELL_SPECIFIC_FOOD;
                    else if (drink)
                        return SPELL_SPECIFIC_DRINK;
                }
                // scrolls effects
                else
                {
                    SpellInfo const* firstRankSpellInfo = GetFirstRankSpell();
                    switch (firstRankSpellInfo->Id)
                    {
                        case 8118: // Strength
                        case 8099: // Stamina
                        case 8112: // Spirit
                        case 8096: // Intellect
                        case 8115: // Agility
                        case 8091: // Armor
                            return SPELL_SPECIFIC_SCROLL;
                        default:
                            break;
                    }
                }
                break;
            }
            case SPELLFAMILY_MAGE:
            {
                // family flags 18(Molten), 25(Frost/Ice), 28(Mage)
                if (SpellFamilyFlags[0] & 0x12040000)
                    return SPELL_SPECIFIC_MAGE_ARMOR;

                // Arcane brillance and Arcane intelect (normal check fails because of flags difference)
                if (SpellFamilyFlags[0] & 0x400)
                    return SPELL_SPECIFIC_MAGE_ARCANE_BRILLANCE;

                if ((SpellFamilyFlags[0] & 0x1000000) && GetEffect(EFFECT_0).IsAura(SPELL_AURA_MOD_CONFUSE))
                    return SPELL_SPECIFIC_MAGE_POLYMORPH;

                break;
            }
            case SPELLFAMILY_WARRIOR:
            {
                if (Id == 12292) // Death Wish
                    return SPELL_SPECIFIC_WARRIOR_ENRAGE;

                break;
            }
            case SPELLFAMILY_WARLOCK:
            {
                // Warlock (Bane of Doom | Bane of Agony | Bane of Havoc)
                if (Id == 603 || Id ==  980 || Id == 80240)
                    return SPELL_SPECIFIC_BANE;

                // only warlock curses have this
                if (Dispel == DISPEL_CURSE)
                    return SPELL_SPECIFIC_CURSE;

                // Warlock (Demon Armor | Demon Skin | Fel Armor)
                if (SpellFamilyFlags[1] & 0x20000020 || SpellFamilyFlags[2] & 0x00000010)
                    return SPELL_SPECIFIC_WARLOCK_ARMOR;

                //seed of corruption and corruption
                if (SpellFamilyFlags[1] & 0x10 || SpellFamilyFlags[0] & 0x2)
                    return SPELL_SPECIFIC_WARLOCK_CORRUPTION;
                break;
            }
            case SPELLFAMILY_PRIEST:
            {
                // Divine Spirit and Prayer of Spirit
                if (SpellFamilyFlags[0] & 0x20)
                    return SPELL_SPECIFIC_PRIEST_DIVINE_SPIRIT;

                break;
            }
            case SPELLFAMILY_HUNTER:
            {
                // only hunter stings have this
                if (Dispel == DISPEL_POISON)
                    return SPELL_SPECIFIC_STING;

                // only hunter aspects have this (but not all aspects in hunter family)
                if (SpellFamilyFlags & flag128(0x00200000, 0x00000000, 0x00001010, 0x00000000))
                    return SPELL_SPECIFIC_ASPECT;

                break;
            }
            case SPELLFAMILY_PALADIN:
            {
                // Collection of all the seal family flags. No other paladin spell has any of those.
                if (SpellFamilyFlags[1] & 0xA2000800)
                    return SPELL_SPECIFIC_SEAL;

                if (SpellFamilyFlags[0] & 0x00002190)
                    return SPELL_SPECIFIC_HAND;

                // only paladin auras have this (for palaldin class family)
                switch (Id)
                {
                    case 465:    // Devotion Aura
                    case 32223:  // Crusader Aura
                    case 183435: // Retribution Aura
                    case 317920: // Concentration Aura
                        return SPELL_SPECIFIC_AURA;
                    default:
                        break;
                }

                break;
            }
            case SPELLFAMILY_SHAMAN:
            {
                // family flags 10 (Lightning), 42 (Earth), 37 (Water), proc shield from T2 8 pieces bonus
                if (SpellFamilyFlags[1] & 0x420
                    || SpellFamilyFlags[0] & 0x00000400
                    || Id == 23552)
                    return SPELL_SPECIFIC_ELEMENTAL_SHIELD;

                break;
            }
            case SPELLFAMILY_DEATHKNIGHT:
                if (Id == 48266 || Id == 48263 || Id == 48265)
                    return SPELL_SPECIFIC_PRESENCE;
                break;
        }

        for (SpellEffectInfo const& effect : GetEffects())
        {
            if (effect.IsEffect(SPELL_EFFECT_APPLY_AURA))
            {
                switch (effect.ApplyAuraName)
                {
                    case SPELL_AURA_MOD_CHARM:
                    case SPELL_AURA_MOD_POSSESS_PET:
                    case SPELL_AURA_MOD_POSSESS:
                    case SPELL_AURA_AOE_CHARM:
                        return SPELL_SPECIFIC_CHARM;
                    case SPELL_AURA_TRACK_CREATURES:
                        /// @workaround For non-stacking tracking spells (We need generic solution)
                        if (Id == 30645) // Gas Cloud Tracking
                            return SPELL_SPECIFIC_NORMAL;
                        [[fallthrough]];
                    case SPELL_AURA_TRACK_RESOURCES:
                    case SPELL_AURA_TRACK_STEALTHED:
                        return SPELL_SPECIFIC_TRACKER;
                    default:
                        break;
                }
            }
        }
        return SPELL_SPECIFIC_NORMAL;
    }();
}

void SpellInfo::_LoadSpellDiminishInfo()
{
    auto diminishingGroupCompute = [this]() -> DiminishingGroup
    {
        if (IsPositive())
            return DIMINISHING_NONE;

        if (HasAura(SPELL_AURA_MOD_TAUNT))
            return DIMINISHING_TAUNT;

        switch (Id)
        {
            case 20549:     // War Stomp (Racial - Tauren)
            case 24394:     // Intimidation
            case 118345:    // Pulverize (Primal Earth Elemental)
            case 118905:    // Static Charge (Capacitor Totem)
                return DIMINISHING_STUN;
            case 107079:    // Quaking Palm
                return DIMINISHING_INCAPACITATE;
            case 155145:    // Arcane Torrent (Racial - Blood Elf)
                return DIMINISHING_SILENCE;
            case 108199:    // Gorefiend's Grasp
            case 191244:    // Sticky Bomb
                return DIMINISHING_AOE_KNOCKBACK;
            default:
                break;
        }

        // Explicit Diminishing Groups
        switch (SpellFamilyName)
        {
            case SPELLFAMILY_GENERIC:
                // Frost Tomb
                if (Id == 48400)
                    return DIMINISHING_NONE;
                // Gnaw
                else if (Id == 47481)
                    return DIMINISHING_STUN;
                // ToC Icehowl Arctic Breath
                else if (Id == 66689)
                    return DIMINISHING_NONE;
                // Black Plague
                else if (Id == 64155)
                    return DIMINISHING_NONE;
                // Screams of the Dead (King Ymiron)
                else if (Id == 51750)
                    return DIMINISHING_NONE;
                // Crystallize (Keristrasza heroic)
                else if (Id == 48179)
                    return DIMINISHING_NONE;
                break;
            case SPELLFAMILY_MAGE:
            {
                // Frost Nova -- 122
                if (SpellFamilyFlags[0] & 0x40)
                    return DIMINISHING_ROOT;
                // Freeze (Water Elemental) -- 33395
                if (SpellFamilyFlags[2] & 0x200)
                    return DIMINISHING_ROOT;

                // Dragon's Breath -- 31661
                if (SpellFamilyFlags[0] & 0x800000)
                    return DIMINISHING_INCAPACITATE;
                // Polymorph -- 118
                if (SpellFamilyFlags[0] & 0x1000000)
                    return DIMINISHING_INCAPACITATE;
                // Ring of Frost -- 82691
                if (SpellFamilyFlags[2] & 0x40)
                    return DIMINISHING_INCAPACITATE;
                // Ice Nova -- 157997
                if (SpellFamilyFlags[2] & 0x800000)
                    return DIMINISHING_INCAPACITATE;
                break;
            }
            case SPELLFAMILY_WARRIOR:
            {
                // Shockwave -- 132168
                if (SpellFamilyFlags[1] & 0x8000)
                    return DIMINISHING_STUN;
                // Storm Bolt -- 132169
                if (SpellFamilyFlags[2] & 0x1000)
                    return DIMINISHING_STUN;

                // Intimidating Shout -- 5246
                if (SpellFamilyFlags[0] & 0x40000)
                    return DIMINISHING_DISORIENT;
                break;
            }
            case SPELLFAMILY_WARLOCK:
            {
                // Mortal Coil -- 6789
                if (SpellFamilyFlags[0] & 0x80000)
                    return DIMINISHING_INCAPACITATE;
                // Banish -- 710
                if (SpellFamilyFlags[1] & 0x8000000)
                    return DIMINISHING_INCAPACITATE;

                // Fear -- 118699
                if (SpellFamilyFlags[1] & 0x400)
                    return DIMINISHING_DISORIENT;
                // Howl of Terror -- 5484
                if (SpellFamilyFlags[1] & 0x8)
                    return DIMINISHING_DISORIENT;

                // Shadowfury -- 30283
                if (SpellFamilyFlags[1] & 0x1000)
                    return DIMINISHING_STUN;
                // Summon Infernal -- 22703
                if (SpellFamilyFlags[0] & 0x1000)
                    return DIMINISHING_STUN;

                // 170995 -- Cripple
                if (Id == 170995)
                    return DIMINISHING_LIMITONLY;
                break;
            }
            case SPELLFAMILY_WARLOCK_PET:
            {
                // Fellash -- 115770
                // Whiplash -- 6360
                if (SpellFamilyFlags[0] & 0x8000000)
                    return DIMINISHING_AOE_KNOCKBACK;

                // Mesmerize (Shivarra pet) -- 115268
                // Seduction (Succubus pet) -- 6358
                if (SpellFamilyFlags[0] & 0x2000000)
                    return DIMINISHING_DISORIENT;

                // Axe Toss (Felguard pet) -- 89766
                if (SpellFamilyFlags[1] & 0x4)
                    return DIMINISHING_STUN;
                break;
            }
            case SPELLFAMILY_DRUID:
            {
                // Maim -- 22570
                if (SpellFamilyFlags[1] & 0x80)
                    return DIMINISHING_STUN;
                // Mighty Bash -- 5211
                if (SpellFamilyFlags[0] & 0x2000)
                    return DIMINISHING_STUN;
                // Rake -- 163505 -- no flags on the stun
                if (Id == 163505)
                    return DIMINISHING_STUN;

                // Incapacitating Roar -- 99, no flags on the stun, 14
                if (SpellFamilyFlags[1] & 0x1)
                    return DIMINISHING_INCAPACITATE;

                // Cyclone -- 33786
                if (SpellFamilyFlags[1] & 0x20)
                    return DIMINISHING_DISORIENT;

                // Solar Beam -- 81261
                if (Id == 81261)
                    return DIMINISHING_SILENCE;

                // Typhoon -- 61391
                if (SpellFamilyFlags[1] & 0x1000000)
                    return DIMINISHING_AOE_KNOCKBACK;
                // Ursol's Vortex -- 118283, no family flags
                if (Id == 118283)
                    return DIMINISHING_AOE_KNOCKBACK;

                // Entangling Roots -- 339
                if (SpellFamilyFlags[0] & 0x200)
                    return DIMINISHING_ROOT;
                // Mass Entanglement -- 102359
                if (SpellFamilyFlags[2] & 0x4)
                    return DIMINISHING_ROOT;
                break;
            }
            case SPELLFAMILY_ROGUE:
            {
                // Between the Eyes -- 199804
                if (SpellFamilyFlags[0] & 0x800000)
                    return DIMINISHING_STUN;
                // Cheap Shot -- 1833
                if (SpellFamilyFlags[0] & 0x400)
                    return DIMINISHING_STUN;
                // Kidney Shot -- 408
                if (SpellFamilyFlags[0] & 0x200000)
                    return DIMINISHING_STUN;

                // Gouge -- 1776
                if (SpellFamilyFlags[0] & 0x8)
                    return DIMINISHING_INCAPACITATE;
                // Sap -- 6770
                if (SpellFamilyFlags[0] & 0x80)
                    return DIMINISHING_INCAPACITATE;

                // Blind -- 2094
                if (SpellFamilyFlags[0] & 0x1000000)
                    return DIMINISHING_DISORIENT;

                // Garrote -- 1330
                if (SpellFamilyFlags[1] & 0x20000000)
                    return DIMINISHING_SILENCE;
                break;
            }
            case SPELLFAMILY_HUNTER:
            {
                // Charge (Tenacity pet) -- 53148, no flags
                if (Id == 53148)
                    return DIMINISHING_ROOT;
                // Ranger's Net -- 200108
                // Tracker's Net -- 212638
                if (Id == 200108 || Id == 212638)
                    return DIMINISHING_ROOT;

                // Binding Shot -- 117526, no flags
                if (Id == 117526)
                    return DIMINISHING_STUN;

                // Freezing Trap -- 3355
                if (SpellFamilyFlags[0] & 0x8)
                    return DIMINISHING_INCAPACITATE;
                // Wyvern Sting -- 19386
                if (SpellFamilyFlags[1] & 0x1000)
                    return DIMINISHING_INCAPACITATE;

                // Bursting Shot -- 224729
                if (SpellFamilyFlags[2] & 0x40)
                    return DIMINISHING_DISORIENT;
                // Scatter Shot -- 213691
                if (SpellFamilyFlags[2] & 0x8000)
                    return DIMINISHING_DISORIENT;

                // Spider Sting -- 202933
                if (Id == 202933)
                    return DIMINISHING_SILENCE;
                break;
            }
            case SPELLFAMILY_PALADIN:
            {
                // Repentance -- 20066
                if (SpellFamilyFlags[0] & 0x4)
                    return DIMINISHING_INCAPACITATE;

                // Blinding Light -- 105421
                if (Id == 105421)
                    return DIMINISHING_DISORIENT;

                // Avenger's Shield -- 31935
                if (SpellFamilyFlags[0] & 0x4000)
                    return DIMINISHING_SILENCE;

                // Hammer of Justice -- 853
                if (SpellFamilyFlags[0] & 0x800)
                    return DIMINISHING_STUN;
                break;
            }
            case SPELLFAMILY_SHAMAN:
            {
                // Hex -- 51514
                // Hex -- 196942 (Voodoo Totem)
                if (SpellFamilyFlags[1] & 0x8000)
                    return DIMINISHING_INCAPACITATE;

                // Thunderstorm -- 51490
                if (SpellFamilyFlags[1] & 0x2000)
                    return DIMINISHING_AOE_KNOCKBACK;

                // Earthgrab Totem -- 64695
                if (SpellFamilyFlags[2] & 0x4000)
                    return DIMINISHING_ROOT;

                // Lightning Lasso -- 204437
                if (SpellFamilyFlags[3] & 0x2000000)
                    return DIMINISHING_STUN;
                break;
            }
            case SPELLFAMILY_DEATHKNIGHT:
            {
                // Chains of Ice -- 96294
                if (Id == 96294)
                    return DIMINISHING_ROOT;

                // Blinding Sleet -- 207167
                if (Id == 207167)
                    return DIMINISHING_DISORIENT;

                // Strangulate -- 47476
                if (SpellFamilyFlags[0] & 0x200)
                    return DIMINISHING_SILENCE;

                // Asphyxiate -- 108194
                if (SpellFamilyFlags[2] & 0x100000)
                    return DIMINISHING_STUN;
                // Gnaw (Ghoul) -- 91800, no flags
                if (Id == 91800)
                    return DIMINISHING_STUN;
                // Monstrous Blow (Ghoul w/ Dark Transformation active) -- 91797
                if (Id == 91797)
                    return DIMINISHING_STUN;
                // Winter is Coming -- 207171
                if (Id == 207171)
                    return DIMINISHING_STUN;
                break;
            }
            case SPELLFAMILY_PRIEST:
            {
                // Holy Word: Chastise -- 200200
                if (SpellFamilyFlags[2] & 0x20 && GetSpellVisual() == 52021)
                    return DIMINISHING_STUN;
                // Mind Bomb -- 226943
                if (Id == 226943)
                    return DIMINISHING_STUN;

                // Mind Control -- 605
                if (SpellFamilyFlags[0] & 0x20000 && GetSpellVisual() == 39068)
                    return DIMINISHING_INCAPACITATE;
                // Holy Word: Chastise -- 200196
                if (SpellFamilyFlags[2] & 0x20 && GetSpellVisual() == 52019)
                    return DIMINISHING_INCAPACITATE;

                // Psychic Scream -- 8122
                if (SpellFamilyFlags[0] & 0x10000)
                    return DIMINISHING_DISORIENT;

                // Silence -- 15487
                if (SpellFamilyFlags[1] & 0x200000 && GetSpellVisual() == 39025)
                    return DIMINISHING_SILENCE;

                // Shining Force -- 204263
                if (Id == 204263)
                    return DIMINISHING_AOE_KNOCKBACK;
                break;
            }
            case SPELLFAMILY_MONK:
            {
                // Disable -- 116706, no flags
                if (Id == 116706)
                    return DIMINISHING_ROOT;

                // Fists of Fury -- 120086
                if (SpellFamilyFlags[1] & 0x800000 && !(SpellFamilyFlags[2] & 0x8))
                    return DIMINISHING_STUN;
                // Leg Sweep -- 119381
                if (SpellFamilyFlags[1] & 0x200)
                    return DIMINISHING_STUN;

                // Incendiary Breath (honor talent) -- 202274, no flags
                if (Id == 202274)
                    return DIMINISHING_INCAPACITATE;
                // Paralysis -- 115078
                if (SpellFamilyFlags[2] & 0x800000)
                    return DIMINISHING_INCAPACITATE;

                // Song of Chi-Ji -- 198909
                if (Id == 198909)
                    return DIMINISHING_DISORIENT;
                break;
            }
            case SPELLFAMILY_DEMON_HUNTER:
            {
                switch (Id)
                {
                    case 179057: // Chaos Nova
                    case 211881: // Fel Eruption
                    case 200166: // Metamorphosis
                    case 205630: // Illidan's Grasp
                        return DIMINISHING_STUN;
                    case 217832: // Imprison
                    case 221527: // Imprison
                        return DIMINISHING_INCAPACITATE;
                    default:
                        break;
                }
                break;
            }
            default:
                break;
        }

        return DIMINISHING_NONE;
    };

    auto diminishingTypeCompute = [](DiminishingGroup group) -> DiminishingReturnsType
    {
        switch (group)
        {
            case DIMINISHING_TAUNT:
            case DIMINISHING_STUN:
                return DRTYPE_ALL;
            case DIMINISHING_LIMITONLY:
            case DIMINISHING_NONE:
                return DRTYPE_NONE;
            default:
                return DRTYPE_PLAYER;
        }
    };

    auto diminishingMaxLevelCompute = [](DiminishingGroup group) -> DiminishingLevels
    {
        switch (group)
        {
            case DIMINISHING_TAUNT:
                return DIMINISHING_LEVEL_TAUNT_IMMUNE;
            case DIMINISHING_AOE_KNOCKBACK:
                return DIMINISHING_LEVEL_2;
            default:
                return DIMINISHING_LEVEL_IMMUNE;
        }
    };

    auto diminishingLimitDurationCompute = [this]() -> int32
    {
        // Explicit diminishing duration
        switch (SpellFamilyName)
        {
            case SPELLFAMILY_MAGE:
            {
                // Dragon's Breath - 3 seconds in PvP
                if (SpellFamilyFlags[0] & 0x800000)
                    return 3 * IN_MILLISECONDS;
                break;
            }
            case SPELLFAMILY_WARLOCK:
            {
                // Cripple - 4 seconds in PvP
                if (Id == 170995)
                    return 4 * IN_MILLISECONDS;
                break;
            }
            case SPELLFAMILY_HUNTER:
            {
                // Binding Shot - 3 seconds in PvP
                if (Id == 117526)
                    return 3 * IN_MILLISECONDS;

                // Wyvern Sting - 6 seconds in PvP
                if (SpellFamilyFlags[1] & 0x1000)
                    return 6 * IN_MILLISECONDS;
                break;
            }
            case SPELLFAMILY_MONK:
            {
                // Paralysis - 4 seconds in PvP regardless of if they are facing you
                if (SpellFamilyFlags[2] & 0x800000)
                    return 4 * IN_MILLISECONDS;
                break;
            }
            case SPELLFAMILY_DEMON_HUNTER:
            {
                switch (Id)
                {
                    case 217832: // Imprison
                    case 221527: // Imprison
                        return 4 * IN_MILLISECONDS;
                    default:
                        break;
                }
                break;
            }
            default:
                break;
        }

        return 8 * IN_MILLISECONDS;
    };

    SpellDiminishInfo diminishInfo;
    diminishInfo.DiminishGroup = diminishingGroupCompute();
    diminishInfo.DiminishReturnType = diminishingTypeCompute(diminishInfo.DiminishGroup);
    diminishInfo.DiminishMaxLevel = diminishingMaxLevelCompute(diminishInfo.DiminishGroup);
    diminishInfo.DiminishDurationLimit = diminishingLimitDurationCompute();

    _diminishInfo = diminishInfo;
}

DiminishingGroup SpellInfo::GetDiminishingReturnsGroupForSpell() const
{
    return _diminishInfo.DiminishGroup;
}

DiminishingReturnsType SpellInfo::GetDiminishingReturnsGroupType() const
{
    return _diminishInfo.DiminishReturnType;
}

DiminishingLevels SpellInfo::GetDiminishingReturnsMaxLevel() const
{
    return _diminishInfo.DiminishMaxLevel;
}

int32 SpellInfo::GetDiminishingReturnsLimitDuration() const
{
    return _diminishInfo.DiminishDurationLimit;
}

void SpellInfo::_LoadImmunityInfo()
{
    std::unique_ptr<SpellEffectInfo::ImmunityInfo> workBuffer = std::make_unique<SpellEffectInfo::ImmunityInfo>();

    for (SpellEffectInfo& effect : _effects)
    {
        uint32 schoolImmunityMask = 0;
        uint32 applyHarmfulAuraImmunityMask = 0;
        uint64 mechanicImmunityMask = 0;
        uint32 dispelImmunityMask = 0;
        uint32 damageImmunityMask = 0;
        uint8 otherImmunityMask = 0;

        int32 miscVal = effect.MiscValue;

        SpellEffectInfo::ImmunityInfo& immuneInfo = *workBuffer;

        switch (effect.ApplyAuraName)
        {
            case SPELL_AURA_MECHANIC_IMMUNITY_MASK:
            {
                if (CreatureImmunities const* creatureImmunities = SpellMgr::GetCreatureImmunities(miscVal))
                {
                    schoolImmunityMask |= creatureImmunities->School.to_ulong();
                    dispelImmunityMask |= creatureImmunities->DispelType.to_ulong();
                    mechanicImmunityMask |= creatureImmunities->Mechanic.to_ullong();
                    otherImmunityMask |= creatureImmunities->Other.AsUnderlyingType();
                    for (SpellEffectName effectType : creatureImmunities->Effect)
                        immuneInfo.SpellEffectImmune.insert(effectType);
                    for (AuraType aura : creatureImmunities->Aura)
                        immuneInfo.AuraTypeImmune.insert(aura);
                }
                break;
            }
            case SPELL_AURA_MECHANIC_IMMUNITY:
            {
                switch (Id)
                {
                    case 42292: // PvP trinket
                    case 59752: // Every Man for Himself
                        mechanicImmunityMask |= IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK;
                        immuneInfo.AuraTypeImmune.insert(SPELL_AURA_USE_NORMAL_MOVEMENT_SPEED);
                        break;
                    case 34471: // The Beast Within
                    case 19574: // Bestial Wrath
                    case 46227: // Medallion of Immunity
                    case 53490: // Bullheaded
                    case 65547: // PvP Trinket
                    case 134946: // Supremacy of the Alliance
                    case 134956: // Supremacy of the Horde
                    case 195710: // Honorable Medallion
                    case 208683: // Gladiator's Medallion
                        mechanicImmunityMask |= IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK;
                        break;
                    case 54508: // Demonic Empowerment
                        mechanicImmunityMask |= (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) | (1 << MECHANIC_STUN);
                        break;
                    default:
                        if (miscVal < 1)
                            break;

                        mechanicImmunityMask |= UI64LIT(1) << miscVal;
                        break;
                }
                break;
            }
            case SPELL_AURA_EFFECT_IMMUNITY:
            {
                immuneInfo.SpellEffectImmune.insert(static_cast<SpellEffectName>(miscVal));
                break;
            }
            case SPELL_AURA_STATE_IMMUNITY:
            {
                immuneInfo.AuraTypeImmune.insert(static_cast<AuraType>(miscVal));
                break;
            }
            case SPELL_AURA_SCHOOL_IMMUNITY:
            {
                schoolImmunityMask |= uint32(miscVal);
                break;
            }
            case SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL:
            {
                applyHarmfulAuraImmunityMask |= uint32(miscVal);
                break;
            }
            case SPELL_AURA_DAMAGE_IMMUNITY:
            {
                damageImmunityMask |= uint32(miscVal);
                break;
            }
            case SPELL_AURA_DISPEL_IMMUNITY:
            {
                dispelImmunityMask = 1u << miscVal;
                break;
            }
            default:
                break;
        }

        immuneInfo.SchoolImmuneMask = schoolImmunityMask;
        immuneInfo.ApplyHarmfulAuraImmuneMask = applyHarmfulAuraImmunityMask;
        immuneInfo.MechanicImmuneMask = mechanicImmunityMask;
        immuneInfo.DispelImmuneMask = dispelImmunityMask;
        immuneInfo.DamageSchoolMask = damageImmunityMask;
        immuneInfo.OtherImmuneMask = otherImmunityMask;

        immuneInfo.AuraTypeImmune.shrink_to_fit();
        immuneInfo.SpellEffectImmune.shrink_to_fit();

        if (immuneInfo.SchoolImmuneMask
            || immuneInfo.ApplyHarmfulAuraImmuneMask
            || immuneInfo.MechanicImmuneMask
            || immuneInfo.DispelImmuneMask
            || immuneInfo.DamageSchoolMask
            || immuneInfo.OtherImmuneMask
            || !immuneInfo.AuraTypeImmune.empty()
            || !immuneInfo.SpellEffectImmune.empty())
        {
            effect._immunityInfo = std::move(workBuffer);
            workBuffer = std::make_unique<SpellEffectInfo::ImmunityInfo>();
        }

        _allowedMechanicMask |= immuneInfo.MechanicImmuneMask;
    }

    if (HasAttribute(SPELL_ATTR5_ALLOW_WHILE_STUNNED))
    {
        switch (Id)
        {
            case 22812: // Barkskin
            case 47585: // Dispersion
                _allowedMechanicMask |=
                    (1 << MECHANIC_STUN) |
                    (1 << MECHANIC_FREEZE) |
                    (1 << MECHANIC_KNOCKOUT) |
                    (1 << MECHANIC_SLEEP);
                break;
            case 49039: // Lichborne, don't allow normal stuns
                break;
            default:
                _allowedMechanicMask |= (1 << MECHANIC_STUN);
                break;
        }
    }

    if (HasAttribute(SPELL_ATTR5_ALLOW_WHILE_CONFUSED))
        _allowedMechanicMask |= (1 << MECHANIC_DISORIENTED);

    if (HasAttribute(SPELL_ATTR5_ALLOW_WHILE_FLEEING))
    {
        switch (Id)
        {
            case 22812: // Barkskin
            case 47585: // Dispersion
                _allowedMechanicMask |= (1 << MECHANIC_FEAR) | (1 << MECHANIC_HORROR);
                break;
            default:
                _allowedMechanicMask |= (1 << MECHANIC_FEAR);
                break;
        }
    }
}

void SpellInfo::_LoadSqrtTargetLimit(int32 maxTargets, int32 numNonDiminishedTargets, Optional<SpellEffIndex> maxTargetsEffectValueHolder,
    Optional<SpellEffIndex> numNonDiminishedTargetsEffectValueHolder)
{
    SqrtDamageAndHealingDiminishing.MaxTargets = maxTargets;
    SqrtDamageAndHealingDiminishing.NumNonDiminishedTargets = numNonDiminishedTargets;

    if (maxTargetsEffectValueHolder)
    {
        if (maxTargetsEffectValueHolder < GetEffects().size())
        {
            SpellEffectInfo const& valueHolder = GetEffect(*maxTargetsEffectValueHolder);
            int32 expectedValue = valueHolder.CalcBaseValue(nullptr, nullptr, 0, -1);
            if (maxTargets != expectedValue)
                TC_LOG_ERROR("spells", "SpellInfo::_LoadSqrtTargetLimit(maxTargets): Spell {} has different value in effect {} than expected, recheck target caps (expected {}, got {})",
                    Id, AsUnderlyingType(*maxTargetsEffectValueHolder), maxTargets, expectedValue);
        }
        else
            TC_LOG_ERROR("spells", "SpellInfo::_LoadSqrtTargetLimit(maxTargets): Spell {} does not have effect {}", Id, AsUnderlyingType(*maxTargetsEffectValueHolder));
    }

    if (numNonDiminishedTargetsEffectValueHolder)
    {
        if (numNonDiminishedTargetsEffectValueHolder < GetEffects().size())
        {
            SpellEffectInfo const& valueHolder = GetEffect(*numNonDiminishedTargetsEffectValueHolder);
            int32 expectedValue = valueHolder.CalcBaseValue(nullptr, nullptr, 0, -1);
            if (numNonDiminishedTargets != expectedValue)
                TC_LOG_ERROR("spells", "SpellInfo::_LoadSqrtTargetLimit(numNonDiminishedTargets): Spell {} has different value in effect {} than expected, recheck target caps (expected {}, got {})",
                    Id, AsUnderlyingType(*numNonDiminishedTargetsEffectValueHolder), numNonDiminishedTargets, expectedValue);
        }
        else
            TC_LOG_ERROR("spells", "SpellInfo::_LoadSqrtTargetLimit(numNonDiminishedTargets): Spell {} does not have effect {}", Id, AsUnderlyingType(*numNonDiminishedTargetsEffectValueHolder));
    }
}

void SpellInfo::ApplyAllSpellImmunitiesTo(Unit* target, SpellEffectInfo const& spellEffectInfo, bool apply) const
{
    SpellEffectInfo::ImmunityInfo const* immuneInfo = spellEffectInfo.GetImmunityInfo();
    if (!immuneInfo)
        return;

    if (uint32 schoolImmunity = immuneInfo->SchoolImmuneMask)
    {
        target->ApplySpellImmune(Id, IMMUNITY_SCHOOL, schoolImmunity, apply);

        if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
        {
            target->RemoveAppliedAuras([this, target, schoolImmunity](AuraApplication const* aurApp) -> bool
            {
                SpellInfo const* auraSpellInfo = aurApp->GetBase()->GetSpellInfo();
                if (auraSpellInfo->Id == Id)                                      // Don't remove self
                    return false;
                if (auraSpellInfo->IsPassive())                                   // Don't remove passive auras
                    return false;
                if (!(auraSpellInfo->GetSchoolMask() & schoolImmunity))           // Check for school mask
                    return false;
                if (!CanDispelAura(auraSpellInfo))
                    return false;
                if (!HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS))
                {
                    WorldObject const* existingAuraCaster = aurApp->GetBase()->GetWorldObjectCaster();
                    if (existingAuraCaster && existingAuraCaster->IsFriendlyTo(target)) // Check spell vs aura possitivity
                        return false;
                }
                return true;
            });
        }

        if (apply && schoolImmunity & SPELL_SCHOOL_MASK_NORMAL)
            target->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::InvulnerabilityBuff);
    }

    if (uint64 mechanicImmunity = immuneInfo->MechanicImmuneMask)
    {
        for (uint32 i = 0; i < MAX_MECHANIC; ++i)
            if (mechanicImmunity & (UI64LIT(1) << i))
                target->ApplySpellImmune(Id, IMMUNITY_MECHANIC, i, apply);

        if (HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
        {
            if (apply)
                target->RemoveAurasWithMechanic(mechanicImmunity, AURA_REMOVE_BY_DEFAULT, Id);
            else
            {
                std::vector<Aura*> aurasToUpdateTargets;
                target->RemoveAppliedAuras([mechanicImmunity, &aurasToUpdateTargets](AuraApplication const* aurApp)
                {
                    Aura* aura = aurApp->GetBase();
                    if (aura->GetSpellInfo()->GetAllEffectsMechanicMask() & mechanicImmunity)
                        aurasToUpdateTargets.push_back(aura);

                    // only update targets, don't remove anything
                    return false;
                });

                for (Aura* aura : aurasToUpdateTargets)
                    aura->UpdateTargetMap(aura->GetCaster());
            }
        }
    }

    if (uint32 dispelImmunity = immuneInfo->DispelImmuneMask)
    {
        for (uint32 i = 0; i < DISPEL_MAX; ++i)
            if (dispelImmunity & (1 << i))
                target->ApplySpellImmune(Id, IMMUNITY_DISPEL, i, apply);

        if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
        {
            target->RemoveAppliedAuras([dispelImmunity](AuraApplication const* aurApp) -> bool
            {
                SpellInfo const* spellInfo = aurApp->GetBase()->GetSpellInfo();
                uint32 dispelMask = spellInfo->GetDispelMask();
                if ((dispelMask & dispelImmunity) == dispelMask)
                    return true;

                return false;
            });
        }
    }

    if (uint32 damageImmunity = immuneInfo->DamageSchoolMask)
    {
        target->ApplySpellImmune(Id, IMMUNITY_DAMAGE, damageImmunity, apply);

        if (apply && damageImmunity & SPELL_SCHOOL_MASK_NORMAL)
            target->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::InvulnerabilityBuff);
    }

    for (AuraType auraType : immuneInfo->AuraTypeImmune)
    {
        target->ApplySpellImmune(Id, IMMUNITY_STATE, auraType, apply);
        if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
            target->RemoveAurasByType(auraType, [this](AuraApplication const* aurApp) -> bool
            {
                return CanDispelAura(aurApp->GetBase()->GetSpellInfo());
            });
    }

    for (SpellEffectName effectType : immuneInfo->SpellEffectImmune)
        target->ApplySpellImmune(Id, IMMUNITY_EFFECT, effectType, apply);

    if (uint8 otherImmuneMask = immuneInfo->OtherImmuneMask)
        target->ApplySpellImmune(Id, IMMUNITY_OTHER, otherImmuneMask, apply);
}

bool SpellInfo::CanSpellProvideImmunityAgainstAura(SpellInfo const* auraSpellInfo) const
{
    if (!auraSpellInfo)
        return false;

    for (SpellEffectInfo const& effectInfo : _effects)
    {
        if (!effectInfo.IsEffect())
            continue;

        SpellEffectInfo::ImmunityInfo const* immuneInfo = effectInfo.GetImmunityInfo();
        if (!immuneInfo)
            continue;

        if (!auraSpellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
            if (uint32 schoolImmunity = immuneInfo->SchoolImmuneMask)
                if ((auraSpellInfo->SchoolMask & schoolImmunity) != 0)
                    return true;

        if (uint64 mechanicImmunity = immuneInfo->MechanicImmuneMask)
            if ((mechanicImmunity & (UI64LIT(1) << auraSpellInfo->Mechanic)) != 0)
                return true;

        if (uint32 dispelImmunity = immuneInfo->DispelImmuneMask)
            if (auraSpellInfo->Dispel == dispelImmunity)
                return true;

        bool immuneToAllEffects = true;
        for (SpellEffectInfo const& auraSpellEffectInfo : auraSpellInfo->GetEffects())
        {
            if (!auraSpellEffectInfo.IsAura())
                continue;

            if (uint64 mechanicImmunity = immuneInfo->MechanicImmuneMask)
                if ((mechanicImmunity & (UI64LIT(1) << auraSpellEffectInfo.Mechanic)) != 0)
                    continue;

            if (AuraType auraName = auraSpellEffectInfo.ApplyAuraName)
            {
                if (immuneInfo->AuraTypeImmune.find(auraName) != immuneInfo->AuraTypeImmune.end())
                    continue;

                if (!auraSpellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES) && !auraSpellInfo->IsPositiveEffect(auraSpellEffectInfo.EffectIndex))
                    if (uint32 applyHarmfulAuraImmunityMask = immuneInfo->ApplyHarmfulAuraImmuneMask)
                        if ((auraSpellInfo->GetSchoolMask() & applyHarmfulAuraImmunityMask) != 0)
                            continue;
            }

            immuneToAllEffects = false;
        }

        if (immuneToAllEffects)
            return true;
    }

    return false;
}

bool SpellInfo::CanSpellEffectProvideImmunityAgainstAuraEffect(SpellEffectInfo const& immunityEffectInfo, SpellInfo const* auraSpellInfo, SpellEffectInfo const& auraEffectInfo) const
{
    SpellEffectInfo::ImmunityInfo const* immuneInfo = immunityEffectInfo.GetImmunityInfo();
    if (!immuneInfo)
        return false;

    if (!auraSpellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
    {
        if (uint32 schoolImmunity = immuneInfo->SchoolImmuneMask)
            if ((auraSpellInfo->SchoolMask & schoolImmunity) != 0)
                return true;

        if (uint32 applyHarmfulAuraImmunityMask = immuneInfo->ApplyHarmfulAuraImmuneMask)
            if ((auraSpellInfo->GetSchoolMask() & applyHarmfulAuraImmunityMask) != 0)
                return true;
    }

    if (uint64 mechanicImmunity = immuneInfo->MechanicImmuneMask)
    {
        if ((mechanicImmunity & (UI64LIT(1) << auraSpellInfo->Mechanic)) != 0)
            return true;
        if ((mechanicImmunity & (UI64LIT(1) << auraEffectInfo.Mechanic)) != 0)
            return true;
    }

    if (uint32 dispelImmunity = immuneInfo->DispelImmuneMask)
        if (auraSpellInfo->Dispel == dispelImmunity)
            return true;

    if (immuneInfo->AuraTypeImmune.find(auraEffectInfo.ApplyAuraName) != immuneInfo->AuraTypeImmune.end())
        return true;

    return false;
}

// based on client Spell_C::CancelsAuraEffect
bool SpellInfo::SpellCancelsAuraEffect(AuraEffect const* aurEff) const
{
    if (!HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
        return false;

    if (aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES))
        return false;

    if (aurEff->GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes::NoImmunity))
        return false;

    for (SpellEffectInfo const& effect : GetEffects())
        if (CanSpellEffectProvideImmunityAgainstAuraEffect(effect, aurEff->GetSpellInfo(), aurEff->GetSpellEffectInfo()))
            return true;

    return false;
}

uint64 SpellInfo::GetAllowedMechanicMask() const
{
    return _allowedMechanicMask;
}

uint64 SpellInfo::GetMechanicImmunityMask(Unit const* caster) const
{
    uint64 casterMechanicImmunityMask = caster->GetMechanicImmunityMask();
    uint64 mechanicImmunityMask = 0;

    if (CanBeInterrupted(nullptr, caster, true))
    {
        if (casterMechanicImmunityMask & (1 << MECHANIC_SILENCE))
            mechanicImmunityMask |= (1 << MECHANIC_SILENCE);

        if (casterMechanicImmunityMask & (1 << MECHANIC_INTERRUPT))
            mechanicImmunityMask |= (1 << MECHANIC_INTERRUPT);
    }

    return mechanicImmunityMask;
}

float SpellInfo::GetMinRange(bool positive /*= false*/) const
{
    if (!RangeEntry)
        return 0.0f;
    return RangeEntry->RangeMin[positive ? 1 : 0];
}

float SpellInfo::GetMaxRange(bool positive /*= false*/, WorldObject* caster /*= nullptr*/, Spell* spell /*= nullptr*/) const
{
    if (!RangeEntry)
        return 0.0f;
    float range = RangeEntry->RangeMax[positive ? 1 : 0];
    if (caster)
        if (Player* modOwner = caster->GetSpellModOwner())
            modOwner->ApplySpellMod(this, SpellModOp::Range, range, spell);

    return range;
}

int32 SpellInfo::CalcDuration(WorldObject const* caster /*= nullptr*/) const
{
    int32 duration = GetDuration();

    if (caster)
        if (Player* modOwner = caster->GetSpellModOwner())
            modOwner->ApplySpellMod(this, SpellModOp::Duration, duration);

    return duration;
}

int32 SpellInfo::GetDuration() const
{
    if (!DurationEntry)
        return IsPassive() ? -1 : 0;
    return (DurationEntry->Duration == -1) ? -1 : abs(DurationEntry->Duration);
}

int32 SpellInfo::GetMaxDuration() const
{
    if (!DurationEntry)
        return IsPassive() ? -1 : 0;
    return (DurationEntry->MaxDuration == -1) ? -1 : abs(DurationEntry->MaxDuration);
}

uint32 SpellInfo::CalcCastTime(Spell* spell /*= nullptr*/) const
{
    int32 castTime = 0;
    if (CastTimeEntry)
        castTime = std::max(CastTimeEntry->Base, CastTimeEntry->Minimum);

    if (castTime <= 0)
        return 0;

    if (spell)
        spell->GetCaster()->ModSpellCastTime(this, castTime, spell);

    if (HasAttribute(SPELL_ATTR0_USES_RANGED_SLOT) && !IsAutoRepeatRangedSpell() && !HasAttribute(SPELL_ATTR9_COOLDOWN_IGNORES_RANGED_WEAPON))
        castTime += 500;

    return (castTime > 0) ? uint32(castTime) : 0;
}

uint32 SpellInfo::GetMaxTicks() const
{
    uint32 totalTicks = 0;
    int32 DotDuration = GetDuration();

    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (effect.IsEffect(SPELL_EFFECT_APPLY_AURA))
        {
            switch (effect.ApplyAuraName)
            {
                case SPELL_AURA_PERIODIC_DAMAGE:
                case SPELL_AURA_PERIODIC_DAMAGE_PERCENT:
                case SPELL_AURA_PERIODIC_HEAL:
                case SPELL_AURA_OBS_MOD_HEALTH:
                case SPELL_AURA_OBS_MOD_POWER:
                case SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT:
                case SPELL_AURA_POWER_BURN:
                case SPELL_AURA_PERIODIC_LEECH:
                case SPELL_AURA_PERIODIC_MANA_LEECH:
                case SPELL_AURA_PERIODIC_ENERGIZE:
                case SPELL_AURA_PERIODIC_DUMMY:
                case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
                case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE:
                case SPELL_AURA_PERIODIC_HEALTH_FUNNEL:
                    // skip infinite periodics
                    if (effect.ApplyAuraPeriod > 0 && DotDuration > 0)
                    {
                        totalTicks = static_cast<uint32>(DotDuration) / effect.ApplyAuraPeriod;
                        if (HasAttribute(SPELL_ATTR5_EXTRA_INITIAL_PERIOD))
                            ++totalTicks;
                    }
                    break;
                default:
                    break;
            }
        }
    }

    return totalTicks;
}

uint32 SpellInfo::GetRecoveryTime() const
{
    return RecoveryTime > CategoryRecoveryTime ? RecoveryTime : CategoryRecoveryTime;
}

Optional<SpellPowerCost> SpellInfo::CalcPowerCost(Powers powerType, bool optionalCost, WorldObject const* caster, SpellSchoolMask schoolMask, Spell* spell /*= nullptr*/) const
{
    // gameobject casts don't use power
    Unit const* unitCaster = caster->ToUnit();
    if (!unitCaster)
        return {};

    auto itr = std::find_if(PowerCosts.cbegin(), PowerCosts.cend(), [powerType](SpellPowerEntry const* spellPowerEntry)
    {
        return spellPowerEntry && spellPowerEntry->PowerType == powerType;
    });
    if (itr == PowerCosts.cend())
        return {};

    return CalcPowerCost(*itr, optionalCost, caster, schoolMask, spell);
}

Optional<SpellPowerCost> SpellInfo::CalcPowerCost(SpellPowerEntry const* power, bool optionalCost, WorldObject const* caster, SpellSchoolMask schoolMask, Spell* spell /*= nullptr*/) const
{
    // gameobject casts don't use power
    Unit const* unitCaster = caster->ToUnit();
    if (!unitCaster)
        return {};

    if (power->RequiredAuraSpellID && !unitCaster->HasAura(power->RequiredAuraSpellID))
        return {};

    // Spell drain all exist power on cast (Only paladin lay of Hands)
    if (HasAttribute(SPELL_ATTR1_USE_ALL_MANA))
    {
        if (optionalCost)
            return {};

        // If power type - health drain all
        if (power->PowerType == POWER_HEALTH)
            return SpellPowerCost{ .Power = POWER_HEALTH, .Amount = int32(unitCaster->GetHealth()) };

        // Else drain all power
        if (power->PowerType < MAX_POWERS)
            return SpellPowerCost{ .Power = Powers(power->PowerType), .Amount = unitCaster->GetPower(Powers(power->PowerType)) };

        TC_LOG_ERROR("spells", "SpellInfo::CalcPowerCost: Unknown power type '{}' in spell {}", power->PowerType, Id);
        return {};
    }

    // Base powerCost
    int32 powerCost = 0;
    if (!optionalCost)
    {
        powerCost = power->ManaCost;
        // PCT cost from total amount
        if (power->PowerCostPct)
        {
            switch (power->PowerType)
            {
                // health as power used
                case POWER_HEALTH:
                    if (G3D::fuzzyEq(power->PowerCostPct, 0.0f))
                        powerCost += int32(CalculatePct(unitCaster->GetMaxHealth(), power->PowerCostMaxPct));
                    else
                        powerCost += int32(CalculatePct(unitCaster->GetMaxHealth(), power->PowerCostPct));
                    break;
                case POWER_MANA:
                    powerCost += int32(CalculatePct(unitCaster->GetCreateMana(), power->PowerCostPct));
                    break;
                case POWER_ALTERNATE_POWER:
                    TC_LOG_ERROR("spells", "SpellInfo::CalcPowerCost: Unknown power type POWER_ALTERNATE_POWER in spell {}", Id);
                    return {};
                default:
                {
                    if (PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(Powers(power->PowerType)))
                    {
                        powerCost += int32(CalculatePct(powerTypeEntry->MaxBasePower, power->PowerCostPct));
                        break;
                    }

                    TC_LOG_ERROR("spells", "SpellInfo::CalcPowerCost: Unknown power type '{}' in spell {}", power->PowerType, Id);
                    return {};
                }
            }
        }
    }
    else
    {
        powerCost = int32(power->OptionalCost);

        if (power->OptionalCostPct)
        {
            switch (power->PowerType)
            {
                // health as power used
                case POWER_HEALTH:
                    powerCost += int32(CalculatePct(unitCaster->GetMaxHealth(), power->OptionalCostPct));
                    break;
                case POWER_MANA:
                    powerCost += int32(CalculatePct(unitCaster->GetCreateMana(), power->OptionalCostPct));
                    break;
                case POWER_ALTERNATE_POWER:
                    TC_LOG_ERROR("spells", "SpellInfo::CalcPowerCost: Unsupported power type POWER_ALTERNATE_POWER in spell {} for optional cost percent", Id);
                    return {};
                default:
                {
                    if (PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(Powers(power->PowerType)))
                    {
                        powerCost += int32(CalculatePct(powerTypeEntry->MaxBasePower, power->OptionalCostPct));
                        break;
                    }

                    TC_LOG_ERROR("spells", "SpellInfo::CalcPowerCost: Unknown power type '{}' in spell {} for optional cost percent", power->PowerType, Id);
                    return {};
                }
            }
        }

        powerCost += unitCaster->GetTotalAuraModifier(SPELL_AURA_MOD_ADDITIONAL_POWER_COST, [this, power](AuraEffect const* aurEff) -> bool
        {
            return aurEff->GetMiscValue() == power->PowerType
                && aurEff->IsAffectingSpell(this);
        });
    }

    bool initiallyNegative = powerCost < 0;

    // Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost)
    if (HasAttribute(SPELL_ATTR4_WEAPON_SPEED_COST_SCALING))
    {
        uint32 speed = 0;
        if (SpellShapeshiftFormEntry const* ss = sSpellShapeshiftFormStore.LookupEntry(unitCaster->GetShapeshiftForm()))
            speed = ss->CombatRoundTime;
        else
        {
            WeaponAttackType slot = BASE_ATTACK;
            if (!HasAttribute(SPELL_ATTR3_REQUIRES_MAIN_HAND_WEAPON) && HasAttribute(SPELL_ATTR3_REQUIRES_OFF_HAND_WEAPON))
                slot = OFF_ATTACK;

            speed = unitCaster->GetBaseAttackTime(slot);
        }

        powerCost += speed / 100;
    }

    if (power->PowerType != POWER_HEALTH)
    {
        if (!optionalCost)
        {
            // Flat mod from caster auras by spell school and power type
            for (AuraEffect const* aura : unitCaster->GetAuraEffectsByType(SPELL_AURA_MOD_POWER_COST_SCHOOL))
            {
                if (!(aura->GetMiscValue() & schoolMask))
                    continue;

                if (!(aura->GetMiscValueB() & (1 << power->PowerType)))
                    continue;

                powerCost += aura->GetAmount();
            }
        }

        // PCT mod from user auras by spell school and power type
        for (auto schoolCostPct : unitCaster->GetAuraEffectsByType(SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT))
        {
            if (!(schoolCostPct->GetMiscValue() & schoolMask))
                continue;

            if (!(schoolCostPct->GetMiscValueB() & (1 << power->PowerType)))
                continue;

            powerCost += CalculatePct(powerCost, schoolCostPct->GetAmount());
        }
    }

    // Apply cost mod by spell
    if (Player* modOwner = unitCaster->GetSpellModOwner())
    {
        Optional<SpellModOp> mod;
        switch (power->OrderIndex)
        {
            case 0:
                mod = SpellModOp::PowerCost0;
                break;
            case 1:
                mod = SpellModOp::PowerCost1;
                break;
            case 2:
                mod = SpellModOp::PowerCost2;
                break;
            default:
                break;
        }

        if (mod)
        {
            if (!optionalCost)
                modOwner->ApplySpellMod(this, *mod, powerCost, spell);
            else
            {
                // optional cost ignores flat modifiers
                int32 flatMod = 0;
                float pctMod = 1.0f;
                modOwner->GetSpellModValues(this, *mod, spell, powerCost, &flatMod, &pctMod);
                powerCost = int32(powerCost * pctMod);
            }
        }
    }

    if (!unitCaster->IsControlledByPlayer() && G3D::fuzzyEq(power->PowerCostPct, 0.0f) && SpellLevel && power->PowerType == POWER_MANA)
    {
        if (HasAttribute(SPELL_ATTR0_SCALES_WITH_CREATURE_LEVEL))
        {
            GtNpcManaCostScalerEntry const* spellScaler = sNpcManaCostScalerGameTable.GetRow(SpellLevel);
            GtNpcManaCostScalerEntry const* casterScaler = sNpcManaCostScalerGameTable.GetRow(unitCaster->GetLevel());
            if (spellScaler && casterScaler)
                powerCost *= casterScaler->Scaler / spellScaler->Scaler;
        }
    }

    if (power->PowerType == POWER_MANA)
        powerCost = float(powerCost) * (1.0f + unitCaster->m_unitData->ManaCostMultiplier);

    // power cost cannot become negative if initially positive
    if (initiallyNegative != (powerCost < 0))
        powerCost = 0;

    return SpellPowerCost{ .Power = Powers(power->PowerType), .Amount = powerCost };
}

std::vector<SpellPowerCost> SpellInfo::CalcPowerCost(WorldObject const* caster, SpellSchoolMask schoolMask, Spell* spell) const
{
    std::vector<SpellPowerCost> costs;
    if (caster->IsUnit())
    {
        costs.reserve(MAX_POWERS_PER_SPELL);

        auto getOrCreatePowerCost = [&](Powers powerType) -> SpellPowerCost&
        {
            auto itr = std::find_if(costs.begin(), costs.end(), [powerType](SpellPowerCost const& cost)
            {
                return cost.Power == powerType;
            });
            if (itr != costs.end())
                return *itr;

            return costs.emplace_back<SpellPowerCost>({ .Power = powerType, .Amount = 0 });
        };

        for (SpellPowerEntry const* power : PowerCosts)
        {
            if (!power)
                continue;

            if (Optional<SpellPowerCost> cost = CalcPowerCost(power, false, caster, schoolMask, spell))
                getOrCreatePowerCost(cost->Power).Amount += cost->Amount;

            if (Optional<SpellPowerCost> optionalCost = CalcPowerCost(power, true, caster, schoolMask, spell))
            {
                SpellPowerCost& cost = getOrCreatePowerCost(optionalCost->Power);
                int32 remainingPower = caster->ToUnit()->GetPower(optionalCost->Power) - cost.Amount;
                if (remainingPower > 0)
                    cost.Amount += std::min(optionalCost->Amount, remainingPower);
            }
        }
    }

    return costs;
}

inline float CalcPPMHasteMod(SpellProcsPerMinuteModEntry const* mod, Unit* caster)
{
    float haste = caster->m_unitData->ModHaste;
    float rangedHaste = caster->m_unitData->ModRangedHaste;
    float spellHaste = caster->m_unitData->ModSpellHaste;
    float regenHaste = caster->m_unitData->ModHasteRegen;

    switch (mod->Param)
    {
        case 1:
            return (1.0f / haste - 1.0f) * mod->Coeff;
        case 2:
            return (1.0f / rangedHaste - 1.0f) * mod->Coeff;
        case 3:
            return (1.0f / spellHaste - 1.0f) * mod->Coeff;
        case 4:
            return (1.0f / regenHaste - 1.0f) * mod->Coeff;
        case 5:
            return (1.0f / std::min(std::min(std::min(haste, rangedHaste), spellHaste), regenHaste) - 1.0f) * mod->Coeff;
        default:
            break;
    }

    return 0.0f;
}

inline float CalcPPMCritMod(SpellProcsPerMinuteModEntry const* mod, Unit* caster)
{
    Player const* player = caster->ToPlayer();
    if (!player)
        return 0.0f;

    float crit = player->m_activePlayerData->CritPercentage;
    float rangedCrit = player->m_activePlayerData->RangedCritPercentage;
    float spellCrit = player->m_activePlayerData->SpellCritPercentage;

    switch (mod->Param)
    {
        case 1:
            return crit * mod->Coeff * 0.01f;
        case 2:
            return rangedCrit * mod->Coeff * 0.01f;
        case 3:
            return spellCrit * mod->Coeff * 0.01f;
        case 4:
            return std::min(std::min(crit, rangedCrit), spellCrit) * mod->Coeff * 0.01f;
        default:
            break;
    }

    return 0.0f;
}

inline float CalcPPMItemLevelMod(SpellProcsPerMinuteModEntry const* mod, int32 itemLevel)
{
    if (itemLevel == mod->Param)
        return 0.0f;

    float itemLevelPoints = GetRandomPropertyPoints(itemLevel, ITEM_QUALITY_RARE, INVTYPE_CHEST, 0);
    float basePoints = GetRandomPropertyPoints(mod->Param, ITEM_QUALITY_RARE, INVTYPE_CHEST, 0);
    if (itemLevelPoints == basePoints)
        return 0.0f;

    return ((itemLevelPoints / basePoints) - 1.0f) * mod->Coeff;
}

float SpellInfo::CalcProcPPM(Unit* caster, int32 itemLevel) const
{
    float ppm = ProcBasePPM;
    if (!caster)
        return ppm;

    for (SpellProcsPerMinuteModEntry const* mod : ProcPPMMods)
    {
        switch (mod->Type)
        {
            case SPELL_PPM_MOD_HASTE:
            {
                ppm *= 1.0f + CalcPPMHasteMod(mod, caster);
                break;
            }
            case SPELL_PPM_MOD_CRIT:
            {
                ppm *= 1.0f + CalcPPMCritMod(mod, caster);
                break;
            }
            case SPELL_PPM_MOD_CLASS:
            {
                if (caster->GetClassMask() & mod->Param)
                    ppm *= 1.0f + mod->Coeff;
                break;
            }
            case SPELL_PPM_MOD_SPEC:
            {
                if (Player* plrCaster = caster->ToPlayer())
                    if (plrCaster->GetPrimarySpecialization() == ChrSpecialization(mod->Param))
                        ppm *= 1.0f + mod->Coeff;
                break;
            }
            case SPELL_PPM_MOD_RACE:
            {
                if (caster->GetRaceMask() & mod->Param)
                    ppm *= 1.0f + mod->Coeff;
                break;
            }
            case SPELL_PPM_MOD_ITEM_LEVEL:
            {
                ppm *= 1.0f + CalcPPMItemLevelMod(mod, itemLevel);
                break;
            }
            case SPELL_PPM_MOD_BATTLEGROUND:
            {
                if (caster->GetMap()->IsBattlegroundOrArena())
                    ppm *= 1.0f + mod->Coeff;
                break;
            }
            default:
                break;
        }
    }

    return ppm;
}

bool SpellInfo::IsRanked() const
{
    return ChainEntry != nullptr;
}

uint8 SpellInfo::GetRank() const
{
    if (!ChainEntry)
        return 1;
    return ChainEntry->rank;
}

SpellInfo const* SpellInfo::GetFirstRankSpell() const
{
    if (!ChainEntry)
        return this;
    return ChainEntry->first;
}

SpellInfo const* SpellInfo::GetLastRankSpell() const
{
    if (!ChainEntry)
        return nullptr;
    return ChainEntry->last;
}

SpellInfo const* SpellInfo::GetNextRankSpell() const
{
    if (!ChainEntry)
        return nullptr;
    return ChainEntry->next;
}

SpellInfo const* SpellInfo::GetPrevRankSpell() const
{
    if (!ChainEntry)
        return nullptr;
    return ChainEntry->prev;
}

SpellInfo const* SpellInfo::GetAuraRankForLevel(uint8 level) const
{
    // ignore passive spells
    if (IsPassive())
        return this;

    // Client ignores spell with these attributes (sub_53D9D0)
    if (HasAttribute(SPELL_ATTR0_AURA_IS_DEBUFF) || HasAttribute(SPELL_ATTR2_ALLOW_LOW_LEVEL_BUFF) || HasAttribute(SPELL_ATTR3_ONLY_PROC_ON_CASTER))
        return this;

    bool needRankSelection = false;
    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (IsPositiveEffect(effect.EffectIndex) &&
            (effect.Effect == SPELL_EFFECT_APPLY_AURA ||
            effect.Effect == SPELL_EFFECT_APPLY_AREA_AURA_PARTY ||
            effect.Effect == SPELL_EFFECT_APPLY_AREA_AURA_RAID) &&
            !effect.Scaling.Coefficient)
        {
            needRankSelection = true;
            break;
        }
    }

    // not required
    if (!needRankSelection)
        return this;

    for (SpellInfo const* nextSpellInfo = this; nextSpellInfo != nullptr; nextSpellInfo = nextSpellInfo->GetPrevRankSpell())
    {
        // if found appropriate level
        if (uint32(level + 10) >= nextSpellInfo->SpellLevel)
            return nextSpellInfo;

        // one rank less then
    }

    // not found
    return nullptr;
}

bool SpellInfo::IsRankOf(SpellInfo const* spellInfo) const
{
    return GetFirstRankSpell() == spellInfo->GetFirstRankSpell();
}

bool SpellInfo::IsDifferentRankOf(SpellInfo const* spellInfo) const
{
    if (Id == spellInfo->Id)
        return false;
    return IsRankOf(spellInfo);
}

bool SpellInfo::IsHighRankOf(SpellInfo const* spellInfo) const
{
    if (ChainEntry && spellInfo->ChainEntry)
        if (ChainEntry->first == spellInfo->ChainEntry->first)
            if (ChainEntry->rank > spellInfo->ChainEntry->rank)
                return true;

    return false;
}

uint32 SpellInfo::GetSpellXSpellVisualId(WorldObject const* caster /*= nullptr*/, WorldObject const* viewer /*= nullptr*/) const
{
    for (SpellXSpellVisualEntry const* visual : _visuals)
    {
        if (visual->CasterPlayerConditionID)
            if (!caster || !caster->IsPlayer() || !ConditionMgr::IsPlayerMeetingCondition(caster->ToPlayer(), visual->CasterPlayerConditionID))
                continue;

        if (UnitConditionEntry const* unitCondition = sUnitConditionStore.LookupEntry(visual->CasterUnitConditionID))
            if (!caster || !caster->IsUnit() || !ConditionMgr::IsUnitMeetingCondition(caster->ToUnit(), Object::ToUnit(viewer), unitCondition))
                continue;

        return visual->ID;
    }

    return 0;
}

uint32 SpellInfo::GetSpellVisual(WorldObject const* caster /*= nullptr*/, WorldObject const* viewer /*= nullptr*/) const
{
    if (SpellXSpellVisualEntry const* visual = sSpellXSpellVisualStore.LookupEntry(GetSpellXSpellVisualId(caster, viewer)))
    {
        //if (visual->LowViolenceSpellVisualID && forPlayer->GetViolenceLevel() operator 2)
        //    return visual->LowViolenceSpellVisualID;

        return visual->SpellVisualID;
    }

    return 0;
}

void SpellInfo::_InitializeExplicitTargetMask()
{
    bool srcSet = false;
    bool dstSet = false;
    uint32 targetMask = Targets;
    // prepare target mask using effect target entries
    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (!effect.IsEffect())
            continue;

        targetMask |= effect.TargetA.GetExplicitTargetMask(srcSet, dstSet);
        targetMask |= effect.TargetB.GetExplicitTargetMask(srcSet, dstSet);

        // add explicit target flags based on spell effects which have EFFECT_IMPLICIT_TARGET_EXPLICIT and no valid target provided
        if (effect.GetImplicitTargetType() != EFFECT_IMPLICIT_TARGET_EXPLICIT)
            continue;

        // extend explicit target mask only if valid targets for effect could not be provided by target types
        uint32 effectTargetMask = effect.GetMissingTargetMask(srcSet, dstSet, targetMask);

        // don't add explicit object/dest flags when spell has no max range
        if (GetMaxRange(true) == 0.0f && GetMaxRange(false) == 0.0f)
            effectTargetMask &= ~(TARGET_FLAG_UNIT_MASK | TARGET_FLAG_GAMEOBJECT | TARGET_FLAG_CORPSE_MASK | TARGET_FLAG_DEST_LOCATION);

        targetMask |= effectTargetMask;
    }

    ExplicitTargetMask = targetMask;
}

inline bool _isPositiveTarget(SpellEffectInfo const& effect)
{
    if (!effect.IsEffect())
        return true;

    return (effect.TargetA.GetCheckType() != TARGET_CHECK_ENEMY &&
        effect.TargetB.GetCheckType() != TARGET_CHECK_ENEMY);
}

bool _isPositiveEffectImpl(SpellInfo const* spellInfo, SpellEffectInfo const& effect, std::unordered_set<std::pair<SpellInfo const*, SpellEffIndex>>& visited)
{
    if (!effect.IsEffect())
        return true;

    // attribute may be already set in DB
    if (!spellInfo->IsPositiveEffect(effect.EffectIndex))
        return false;

    // passive auras like talents are all positive
    if (spellInfo->IsPassive())
        return true;

    // not found a single positive spell with this attribute
    if (spellInfo->HasAttribute(SPELL_ATTR0_AURA_IS_DEBUFF))
        return false;

    if (spellInfo->HasAttribute(SPELL_ATTR4_AURA_IS_BUFF))
        return true;

    if (effect.EffectAttributes.HasFlag(SpellEffectAttributes::IsHarmful))
        return false;

    visited.insert({ spellInfo, effect.EffectIndex });

    //We need scaling level info for some auras that compute bp 0 or positive but should be debuffs
    float bpScalePerLevel = effect.RealPointsPerLevel;
    int32 bp = effect.CalcValue();
    switch (spellInfo->SpellFamilyName)
    {
        case SPELLFAMILY_GENERIC:
            switch (spellInfo->Id)
            {
                case 40268: // Spiritual Vengeance, Teron Gorefiend, Black Temple
                case 61987: // Avenging Wrath Marker
                case 61988: // Divine Shield exclude aura
                case 64412: // Phase Punch, Algalon the Observer, Ulduar
                case 72410: // Rune of Blood, Saurfang, Icecrown Citadel
                case 71204: // Touch of Insignificance, Lady Deathwhisper, Icecrown Citadel
                    return false;
                case 24732: // Bat Costume
                case 30877: // Tag Murloc
                case 61716: // Rabbit Costume
                case 61734: // Noblegarden Bunny
                case 62344: // Fists of Stone
                case 50344: // Dream Funnel
                case 61819: // Manabonked! (item)
                case 61834: // Manabonked! (minigob)
                case 73523: // Rigor Mortis
                    return true;
                default:
                    break;
            }
            break;
        case SPELLFAMILY_ROGUE:
            switch (spellInfo->Id)
            {
                // Envenom must be considered as a positive effect even though it deals damage
                case 32645: // Envenom
                    return true;
                case 40251: // Shadow of Death, Teron Gorefiend, Black Temple
                    return false;
                default:
                    break;
            }
            break;
        case SPELLFAMILY_WARRIOR:
            // Slam, Execute
            if ((spellInfo->SpellFamilyFlags[0] & 0x20200000) != 0)
                return false;
            break;
        default:
            break;
    }

    switch (spellInfo->Mechanic)
    {
        case MECHANIC_IMMUNE_SHIELD:
            return true;
        default:
            break;
    }

    // Special case: effects which determine positivity of whole spell
    if (spellInfo->HasAttribute(SPELL_ATTR1_AURA_UNIQUE))
    {
        // check for targets, there seems to be an assortment of dummy triggering spells that should be negative
        for (SpellEffectInfo const& otherEffect : spellInfo->GetEffects())
            if (!_isPositiveTarget(otherEffect))
                return false;
    }

    for (SpellEffectInfo const& otherEffect : spellInfo->GetEffects())
    {
        switch (otherEffect.Effect)
        {
            case SPELL_EFFECT_HEAL:
            case SPELL_EFFECT_LEARN_SPELL:
            case SPELL_EFFECT_SKILL_STEP:
            case SPELL_EFFECT_HEAL_PCT:
                return true;
            case SPELL_EFFECT_INSTAKILL:
                if (otherEffect.EffectIndex != effect.EffectIndex && // for spells like 38044: instakill effect is negative but auras on target must count as buff
                    otherEffect.TargetA.GetTarget() == effect.TargetA.GetTarget() &&
                    otherEffect.TargetB.GetTarget() == effect.TargetB.GetTarget())
                    return false;
                break;
            default:
                break;
        }

        if (otherEffect.IsAura())
        {
            switch (otherEffect.ApplyAuraName)
            {
                case SPELL_AURA_MOD_STEALTH:
                case SPELL_AURA_MOD_UNATTACKABLE:
                    return true;
                case SPELL_AURA_SCHOOL_HEAL_ABSORB:
                case SPELL_AURA_EMPATHY:
                case SPELL_AURA_MOD_SPELL_DAMAGE_FROM_CASTER:
                case SPELL_AURA_PREVENTS_FLEEING:
                    return false;
                default:
                    break;
            }
        }
    }

    switch (effect.Effect)
    {
        case SPELL_EFFECT_WEAPON_DAMAGE:
        case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL:
        case SPELL_EFFECT_NORMALIZED_WEAPON_DMG:
        case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE:
        case SPELL_EFFECT_SCHOOL_DAMAGE:
        case SPELL_EFFECT_ENVIRONMENTAL_DAMAGE:
        case SPELL_EFFECT_HEALTH_LEECH:
        case SPELL_EFFECT_INSTAKILL:
        case SPELL_EFFECT_POWER_DRAIN:
        case SPELL_EFFECT_STEAL_BENEFICIAL_BUFF:
        case SPELL_EFFECT_INTERRUPT_CAST:
        case SPELL_EFFECT_PICKPOCKET:
        case SPELL_EFFECT_GAMEOBJECT_DAMAGE:
        case SPELL_EFFECT_DURABILITY_DAMAGE:
        case SPELL_EFFECT_DURABILITY_DAMAGE_PCT:
        case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
        case SPELL_EFFECT_TAMECREATURE:
        case SPELL_EFFECT_DISTRACT:
            return false;
        case SPELL_EFFECT_ENERGIZE:
        case SPELL_EFFECT_ENERGIZE_PCT:
        case SPELL_EFFECT_HEAL_PCT:
        case SPELL_EFFECT_HEAL_MAX_HEALTH:
        case SPELL_EFFECT_HEAL_MECHANICAL:
            return true;
        case SPELL_EFFECT_KNOCK_BACK:
        case SPELL_EFFECT_CHARGE:
        case SPELL_EFFECT_PERSISTENT_AREA_AURA:
        case SPELL_EFFECT_ATTACK_ME:
        case SPELL_EFFECT_POWER_BURN:
            // check targets
            if (!_isPositiveTarget(effect))
                return false;
            break;
        case SPELL_EFFECT_DISPEL:
            // non-positive dispel
            switch (effect.MiscValue)
            {
                case DISPEL_STEALTH:
                case DISPEL_INVISIBILITY:
                case DISPEL_ENRAGE:
                    return false;
                default:
                    break;
            }

            // also check targets
            if (!_isPositiveTarget(effect))
                return false;
            break;
        case SPELL_EFFECT_DISPEL_MECHANIC:
            if (!_isPositiveTarget(effect))
            {
                // non-positive mechanic dispel on negative target
                switch (effect.MiscValue)
                {
                    case MECHANIC_BANDAGE:
                    case MECHANIC_SHIELD:
                    case MECHANIC_MOUNT:
                    case MECHANIC_INVULNERABILITY:
                        return false;
                    default:
                        break;
                }
            }
            break;
        case SPELL_EFFECT_THREAT:
        case SPELL_EFFECT_MODIFY_THREAT_PERCENT:
            // check targets AND basepoints
            if (!_isPositiveTarget(effect) && bp > 0)
                return false;
            break;
        default:
            break;
    }

    if (effect.IsAura())
    {
        // non-positive aura use
        switch (effect.ApplyAuraName)
        {
            case SPELL_AURA_MOD_STAT:                   // dependent from basepoint sign (negative -> negative)
            case SPELL_AURA_MOD_SKILL:
            case SPELL_AURA_MOD_SKILL_2:
            case SPELL_AURA_MOD_DODGE_PERCENT:
            case SPELL_AURA_MOD_HEALING_DONE:
            case SPELL_AURA_MOD_DAMAGE_DONE_CREATURE:
            case SPELL_AURA_OBS_MOD_HEALTH:
            case SPELL_AURA_OBS_MOD_POWER:
            case SPELL_AURA_MOD_CRIT_PCT:
            case SPELL_AURA_MOD_HIT_CHANCE:
            case SPELL_AURA_MOD_SPELL_HIT_CHANCE:
            case SPELL_AURA_MOD_SPELL_CRIT_CHANCE:
            case SPELL_AURA_MOD_RANGED_HASTE:
            case SPELL_AURA_MOD_MELEE_RANGED_HASTE:
            case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK:
            case SPELL_AURA_HASTE_SPELLS:
            case SPELL_AURA_MOD_RECOVERY_RATE_BY_SPELL_LABEL:
            case SPELL_AURA_MOD_DETECT_RANGE:
            case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT:
            case SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE:
            case SPELL_AURA_MOD_INCREASE_SWIM_SPEED:
            case SPELL_AURA_MOD_PERCENT_STAT:
            case SPELL_AURA_MOD_INCREASE_HEALTH:
            case SPELL_AURA_MOD_SPEED_ALWAYS:
                if (bp < 0 || bpScalePerLevel < 0) //TODO: What if both are 0? Should it be a buff or debuff?
                    return false;
                break;
            case SPELL_AURA_MOD_ATTACKSPEED:            // some buffs have negative bp, check both target and bp
            case SPELL_AURA_MOD_MELEE_HASTE:
            case SPELL_AURA_MOD_DAMAGE_DONE:
            case SPELL_AURA_MOD_RESISTANCE:
            case SPELL_AURA_MOD_RESISTANCE_PCT:
            case SPELL_AURA_MOD_RATING:
            case SPELL_AURA_MOD_ATTACK_POWER:
            case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
            case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE:
            case SPELL_AURA_MOD_SPEED_SLOW_ALL:
            case SPELL_AURA_MELEE_SLOW:
            case SPELL_AURA_MOD_ATTACK_POWER_PCT:
            case SPELL_AURA_MOD_HEALING_DONE_PERCENT:
            case SPELL_AURA_MOD_HEALING_PCT:
                if (!_isPositiveTarget(effect) || bp < 0)
                    return false;
                break;
            case SPELL_AURA_MOD_DAMAGE_TAKEN:           // dependent from basepoint sign (positive -> negative)
            case SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN:
            case SPELL_AURA_MOD_MELEE_DAMAGE_TAKEN_PCT:
            case SPELL_AURA_MOD_POWER_COST_SCHOOL:
            case SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT:
            case SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT:
                if (bp > 0)
                    return false;
                break;
            case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN:   // check targets and basepoints (ex Recklessness)
                if (!_isPositiveTarget(effect) && bp > 0)
                    return false;
                break;
            case SPELL_AURA_MOD_HEALTH_REGEN_PERCENT:   // check targets and basepoints (target enemy and negative bp -> negative)
                if (!_isPositiveTarget(effect) && bp < 0)
                    return false;
                break;
            case SPELL_AURA_ADD_TARGET_TRIGGER:
                return true;
            case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE:
            case SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT:
                if (SpellInfo const* spellTriggeredProto = sSpellMgr->GetSpellInfo(effect.TriggerSpell, spellInfo->Difficulty))
                {
                    // negative targets of main spell return early
                    for (SpellEffectInfo const& spellTriggeredEffect : spellTriggeredProto->GetEffects())
                    {
                        // already seen this
                        if (visited.count({ spellTriggeredProto, spellTriggeredEffect.EffectIndex }) > 0)
                            continue;

                        if (!spellTriggeredEffect.IsEffect())
                            continue;

                        // if non-positive trigger cast targeted to positive target this main cast is non-positive
                        // this will place this spell auras as debuffs
                        if (_isPositiveTarget(spellTriggeredEffect) && !_isPositiveEffectImpl(spellTriggeredProto, spellTriggeredEffect, visited))
                            return false;
                    }
                }
                break;
            case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
            case SPELL_AURA_MOD_STUN:
            case SPELL_AURA_TRANSFORM:
            case SPELL_AURA_MOD_DECREASE_SPEED:
            case SPELL_AURA_MOD_FEAR:
            case SPELL_AURA_MOD_TAUNT:
                // special auras: they may have non negative target but still need to be marked as debuff
                // checked again after all effects (SpellInfo::_InitializeSpellPositivity)
            case SPELL_AURA_MOD_PACIFY:
            case SPELL_AURA_MOD_PACIFY_SILENCE:
            case SPELL_AURA_MOD_DISARM:
            case SPELL_AURA_MOD_DISARM_OFFHAND:
            case SPELL_AURA_MOD_DISARM_RANGED:
            case SPELL_AURA_MOD_CHARM:
            case SPELL_AURA_AOE_CHARM:
            case SPELL_AURA_MOD_POSSESS:
            case SPELL_AURA_MOD_LANGUAGE:
            case SPELL_AURA_DAMAGE_SHIELD:
            case SPELL_AURA_PROC_TRIGGER_SPELL:
            case SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE:
            case SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE:
            case SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE:
            case SPELL_AURA_MOD_ATTACKER_MELEE_CRIT_CHANCE:
            case SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE:
            case SPELL_AURA_DUMMY:
            case SPELL_AURA_PERIODIC_DUMMY:
            case SPELL_AURA_MOD_HEALING:
            case SPELL_AURA_MOD_WEAPON_CRIT_PERCENT:
            case SPELL_AURA_POWER_BURN:
            case SPELL_AURA_MOD_COOLDOWN:
            case SPELL_AURA_MOD_CHARGE_COOLDOWN:
            case SPELL_AURA_MOD_INCREASE_SPEED:
            case SPELL_AURA_MOD_PARRY_PERCENT:
            case SPELL_AURA_SET_VEHICLE_ID:
            case SPELL_AURA_PERIODIC_ENERGIZE:
            case SPELL_AURA_EFFECT_IMMUNITY:
            case SPELL_AURA_OVERRIDE_CLASS_SCRIPTS:
            case SPELL_AURA_MOD_SHAPESHIFT:
            case SPELL_AURA_MOD_THREAT:
            case SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE:
                // check target for positive and negative spells
                if (!_isPositiveTarget(effect))
                    return false;
                break;
            case SPELL_AURA_MOD_CONFUSE:
            case SPELL_AURA_CHANNEL_DEATH_ITEM:
            case SPELL_AURA_MOD_ROOT:
            case SPELL_AURA_MOD_ROOT_2:
            case SPELL_AURA_MOD_SILENCE:
            case SPELL_AURA_MOD_DETAUNT:
            case SPELL_AURA_GHOST:
            case SPELL_AURA_PERIODIC_LEECH:
            case SPELL_AURA_PERIODIC_MANA_LEECH:
            case SPELL_AURA_MOD_STALKED:
            case SPELL_AURA_PREVENT_RESURRECTION:
            case SPELL_AURA_PERIODIC_DAMAGE:
            case SPELL_AURA_PERIODIC_WEAPON_PERCENT_DAMAGE:
            case SPELL_AURA_PERIODIC_DAMAGE_PERCENT:
            case SPELL_AURA_MELEE_ATTACK_POWER_ATTACKER_BONUS:
            case SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS:
                return false;
            case SPELL_AURA_MECHANIC_IMMUNITY:
            {
                // non-positive immunities
                switch (effect.MiscValue)
                {
                    case MECHANIC_BANDAGE:
                    case MECHANIC_SHIELD:
                    case MECHANIC_MOUNT:
                    case MECHANIC_INVULNERABILITY:
                        return false;
                    default:
                        break;
                }
                break;
            }
            case SPELL_AURA_ADD_FLAT_MODIFIER:          // mods
            case SPELL_AURA_ADD_PCT_MODIFIER:
            case SPELL_AURA_ADD_FLAT_MODIFIER_BY_SPELL_LABEL:
            case SPELL_AURA_ADD_PCT_MODIFIER_BY_SPELL_LABEL:
            {
                switch (SpellModOp(effect.MiscValue))
                {
                    case SpellModOp::ChangeCastTime:        // dependent from basepoint sign (positive -> negative)
                    case SpellModOp::Period:
                    case SpellModOp::PowerCostOnMiss:
                    case SpellModOp::StartCooldown:
                        if (bp > 0)
                            return false;
                        break;
                    case SpellModOp::Cooldown:
                    case SpellModOp::PowerCost0:
                    case SpellModOp::PowerCost1:
                    case SpellModOp::PowerCost2:
                        if (!spellInfo->IsPositive() && bp > 0) // dependent on prev effects too (ex Arcane Power)
                            return false;
                        break;
                    case SpellModOp::PointsIndex0:          // always positive
                    case SpellModOp::PointsIndex1:
                    case SpellModOp::PointsIndex2:
                    case SpellModOp::PointsIndex3:
                    case SpellModOp::PointsIndex4:
                    case SpellModOp::Points:
                    case SpellModOp::Hate:
                    case SpellModOp::ChainAmplitude:
                    case SpellModOp::Amplitude:
                        return true;
                    case SpellModOp::Duration:
                    case SpellModOp::CritChance:
                    case SpellModOp::HealingAndDamage:
                    case SpellModOp::ChainTargets:
                        if (!spellInfo->IsPositive() && bp < 0) // dependent on prev effects too
                            return false;
                        break;
                    default:                                // dependent from basepoint sign (negative -> negative)
                        if (bp < 0)
                            return false;
                        break;
                }
                break;
            }
            default:
                break;
        }
    }

    // negative spell if triggered spell is negative
    if (!effect.ApplyAuraName && effect.TriggerSpell)
    {
        if (SpellInfo const* spellTriggeredProto = sSpellMgr->GetSpellInfo(effect.TriggerSpell, spellInfo->Difficulty))
        {
            // spells with at least one negative effect are considered negative
            // some self-applied spells have negative effects but in self casting case negative check ignored.
            for (SpellEffectInfo const& spellTriggeredEffect : spellTriggeredProto->GetEffects())
            {
                // already seen this
                if (visited.count({ spellTriggeredProto, spellTriggeredEffect.EffectIndex }) > 0)
                    continue;

                if (!spellTriggeredEffect.IsEffect())
                    continue;

                if (!_isPositiveEffectImpl(spellTriggeredProto, spellTriggeredEffect, visited))
                    return false;
            }
        }
    }

    // ok, positive
    return true;
}

void SpellInfo::_InitializeSpellPositivity()
{
    std::unordered_set<std::pair<SpellInfo const*, SpellEffIndex /*effIndex*/>> visited;

    for (SpellEffectInfo const& effect : GetEffects())
        if (!_isPositiveEffectImpl(this, effect, visited))
            NegativeEffects[effect.EffectIndex] = true;

    // additional checks after effects marked
    for (SpellEffectInfo const& effect : GetEffects())
    {
        if (!effect.IsEffect() || !IsPositiveEffect(effect.EffectIndex))
            continue;

        switch (effect.ApplyAuraName)
        {
            // has other non positive effect?
            // then it should be marked negative if has same target as negative effect (ex 8510, 8511, 8893, 10267)
            case SPELL_AURA_DUMMY:
            case SPELL_AURA_MOD_STUN:
            case SPELL_AURA_MOD_FEAR:
            case SPELL_AURA_MOD_TAUNT:
            case SPELL_AURA_TRANSFORM:
            case SPELL_AURA_MOD_ATTACKSPEED:
            case SPELL_AURA_MOD_DECREASE_SPEED:
            {
                for (size_t j = effect.EffectIndex + 1; j < GetEffects().size(); ++j)
                    if (!IsPositiveEffect(j)
                        && effect.TargetA.GetTarget() == GetEffect(SpellEffIndex(j)).TargetA.GetTarget()
                        && effect.TargetB.GetTarget() == GetEffect(SpellEffIndex(j)).TargetB.GetTarget())
                        NegativeEffects[effect.EffectIndex] = true;
                break;
            }
            default:
                break;
        }
    }
}

void SpellInfo::_UnloadImplicitTargetConditionLists()
{
    // find the same instances of ConditionList and delete them.
    for (SpellEffectInfo& effect : _effects)
        effect.ImplicitTargetConditions = nullptr;
}

bool SpellInfo::MeetsFutureSpellPlayerCondition(Player const* player) const
{
    if (ShowFutureSpellPlayerConditionID == 0)
        return false;

    return ConditionMgr::IsPlayerMeetingCondition(player, ShowFutureSpellPlayerConditionID);
}

bool SpellInfo::HasLabel(uint32 labelId) const
{
    return Labels.find(labelId) != Labels.end();
}
